堆的介绍和其构造 C++

C++STL里相应的数据结构就是priority_queue了,字面意思,优先队列
首先需要区分的是堆、堆栈、栈的概念,我刚学的时候也经常堆、堆栈、栈概念分不清

从数据结构的角度来说:

堆heap:一种数据结构,可以把它看成一颗完全二叉树,并且该完全二叉树满足任何一个非叶节点的值都不大于其左右子节点的值(最小堆,反之即最大堆),可保证其根节点一定是该堆的最大(最小)值,这种特性的一个应用就是用来排序,也即堆排序。
栈stack:同样也是一种数据结构,满足先进后出。
堆栈:其实就是栈,不知道这个名词是啥时候出来的,就因为这个名词混淆了一大批人。

需注意的是,堆和栈的实现可用链表、也可用数组,只要能实现其特性就行,别太死板

从操作系统的角度来说:

堆:堆区,和数据结构中的堆不是一回事,在堆区分配的内存由程序员管理,堆区的大小取决于你的虚拟内存大小,再扯远点就关系到地址线位数。
java、C#啥的堆区分配的内容的可由JVM、CLR进行管理,自动回收垃圾,不然就产生内存泄露问题。
也即,

C# object o = new object(); //具体new出来是分配在啥地方关系到值类型和引用类型,此处不细说

此object并不需要程序员操心其析构,具体怎么做不是此文的要点,当然C++也不是说一定得程序员管理,现代C++里的智能指针也可达到相应的效果
栈:由操作系统进行分配和释放,其结构和数据结构的栈类似,弹出和压栈通过移动栈顶指针即可,栈的大小一般是固定的,具体为多少看具体实现。我们平常的函数调用就是通过栈来完成的
就如同

A
AA
AAA(最外层A执行完毕)
AA
A
函数执行完毕

其他的区别我就不详细说了,再说更扯远了。

堆的建立

以下过程取自 天勤版数据结构高分笔记,我也懒得自己想例子了
以序列 49 38 65 97 76 13 27 49 为例,构造一个最大堆
此时的序列对应的二叉树为:

           49
       38      65
    97   76  13   27
 49

从其最靠后的子树根节点开始,此处即97开始,检查97和其左右子节点的大小关系,此处97最大,不用调整

           49
       38      65
    97   76  13   27
 49

然后到65,仍然满足大小关系,到38以后,因为38<97&&38<76,选择最大的97,交换38、97的位置(需不停将要下沉的节点反复下沉,直到满足大小关系)

           49
       97      65
   |38|  76  13   27
 49

38比49小,继续下沉

           49
       97      65
    49   76  13   27
 |38|

现在检查49,97>49&&97>65,当前最大的子节点为97,交换49、97

           97
       |49|    65
    49    76  13   27
 38

76比49大,49往76下沉。

           97
       76       65
    49  |49|  13   27
 38

现在都满足大小关系了,最大堆即构造完成。

那么怎么通过最大堆来排序呢,只需每次将堆顶弹出即可,当然弹出后要对新的堆顶元素进行下沉处理,一般实现是将最后一个元素与堆顶交换,然后将堆的size-1,接着对新堆顶进行下沉处理。

注意::为方便遍历,container[0]是空出来的,因为满二叉树的根节点只需简单的下标*2,下标*2+1即可获得其左右子节点。
以下实现为一个最小堆

class JpBinaryHeap
    {
    private:
        enum
        {
            DEFAULTCAP = 100
        };//采用enum可以避免 对DEFAULTCAP取地址这种误操作

        vector<ComparableT> container;
        int currentSize;
    public:
        explicit JpBinaryHeap(int cap = DEFAULTCAP);

        JpBinaryHeap(const initializer_list<ComparableT>& lis);

        void insert(const ComparableT& val);

        bool isEmpty()
        {
            return container.count() <= 1 ? true : false;
        }


        void deleteMin();

        int findMin() const
        {
            return container[1]; //根节点即为最小值
        }

        void clear();
    };


    template <typename ComparableT>
    JpBinaryHeap<ComparableT>::JpBinaryHeap(int cap): currentSize(0)
    {
        container.resize(cap);
    }

    template <typename ComparableT>
    JpBinaryHeap<ComparableT>::JpBinaryHeap(initializer_list<ComparableT> lis): currentSize(0)
    {
        container.resize(lis.size() * 2); 
        container[0] = -1;
        for (const auto& i : lis)
            insert(i);
    }

    template <typename ComparableT>
    void JpBinaryHeap<ComparableT>::insert(const ComparableT& val)
    {
        if (currentSize == container.size() - 1)
            container.resize(container.size() * 2); //对container的扩容操作
        ++currentSize;
        int hole = currentSize;

        for (; hole > 1 && val < container[hole / 2]; hole /= 2)
            container[hole] = container[hole / 2];
        container[hole] = val;
        //以上相当于把新插入的节点放在最后一个的位置,然后反复和其根节点比较,直到上升了合适的位置
    }

    template <typename ComparableT>
    void JpBinaryHeap<ComparableT>::deleteMin()
    {
        if (isEmpty())
            throw new exception("haven't contains enough elements");
        auto lastEle = container[currentSize];
        --currentSize;
        int hole = 1;
        int child;
        for (; hole * 2 <= currentSize; hole = child)
        {
            child = hole * 2;
            if (container[child] > container[child + 1])//将child设置为左右子节点中最大的那个的下标
                child += 1;
            if (container[child] < lastEle)
                container[hole] = container[child];
            else//最大的子节点值仍比根节点小的话,显然就已下沉到合适的位置
                break;
        }
        container[hole] = lastEle;
    }

    template <typename ComparableT>
    void JpBinaryHeap<ComparableT>::clear()
    {
        container.clear();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值