二叉堆

二叉堆

 

 

我们知道堆栈是一种LIFO(后进先出)结构,队列是一种FIFO(先进先出)结构,而二叉堆是一种最小值先出的数据结构,因此二叉堆很适合用来做排序

二叉树的性质:二叉堆是一棵完全二叉树,且任意一个结点的键值总是小于或等于其子结点的键值,

二叉堆采用数组来存储(按广度优先遍历的顺序),而没有像普通的树结构使用指针来表示节点间的关系。

如下图所示:

 

如图,最小的一个元素就是数组第一个元素。

将二叉堆按最广优先遍历的顺序编号从0到N-1,根据完全二叉树的性质,则第n个结点的子结点分别是2n+1和2n+2,其父结点是(n-1)/2。并且,叶子结点的下标是从 (N-1)/2开始,一直到N-1。这样,对于数组中的任意一个元素,我们可以很方便的计算出它的父节点、孩子节点所在的位置(即数组下标)。

 


插入

如果要在二叉堆中插入或删除一个元素,必须保证堆性质仍能满足。

新插入的节点new 放在完全二叉树最后的位置,再和父节点比较。如果new节点比父节点小,那么交换两者。交换之后,继续和新的父节点比较…… 直到new节点不比父节点小,或者new节点成为根节点。这样得到的树,就恢复了堆的性质。

 

 

 

 


 

删除

删除操作一定是踢出数组的第一个元素(即根节点),这么来第一个元素以前的位置就成了空位,让最后一个节点 last成为新的节点,从而构成一个新的二叉树。再将last节点不断的和子节点比较。如果last节点比两个子节点中小的那一个大,则和该子节点交换。直到last节点不大于任一子节点都小,或者last节点成为叶节点。

 

 

 


C++代码实现:

class heap {
public:
    void insert(int val) {
        array.push_back(val);
        up(array.size() - 1);
    }

    int del() {
        if (array.empty()) return -1;
        int res = array.front();
        array[0] = array[array.size() - 1];
        array.resize(array.size() - 1);

        down(0);
        return res;
    }

    void up(int idx) {
        while (idx > 0 && parent(idx) >= 0
               && array[parent(idx)] > array[idx]) {

            int p = parent(idx);
            swap(p, idx);
            idx = p;
        }
    }

    void down(int idx) {
        while (left(idx) < array.size()) {
            int l = left(idx);
            int r = right(idx);

            // 如果右孩子存在且比左孩子小
            if (r < array.size() && array[r] < array[l]) {
                l = r;
            }

            if (array[idx] < array[l]) break;
            swap(l, idx);
            idx = l;
        }
    }

    void swap(int a, int b) {
        int tmp = array[a];
        array[a] = array[b];
        array[b] = tmp;
    }

    // 父节点的索引
    int parent(int root) {
        return (root - 1) / 2;
    }

    // 左孩子的索引
    int left(int root) {
        return root * 2 + 1;
    }

    // 右孩子的索引
    int right(int root) {
        return root * 2 + 2;
    }

public:
    vector<int> array;
};

 

 

 


 

最小堆和最大堆

最大堆:父结点的键值总是大于或等于任何一个子结点的键值;最大堆常用于排序算法。
最小堆:父结点的键值总是小于或等于任何一个子结点的键值;最小堆常用于优先队列。

堆的意义就在于:最快的找到最大/最小值,在堆结构中插入一个值重新构造堆结构,取走最大/最下值后重新构造堆结构;其时间复杂度为O(logN);

堆实践中用途不在于排序,其主要用在调度算法中,比如优先级调度,每次取优先级最高的,时间驱动,取时间最小/等待最长的 等等 ,分为最大堆/最小堆。

 

topK问题

求前K个最大的数,用最小堆;求前K个最小的数,用最大堆。

以求N个元素中前K个最大数为例,我们首先取这N个元素中的前K个元素来建立一个由K个元素组成的小顶堆,这样堆顶元素便是当前已读取元素中的第K大者;然后,依次读取剩下的N-K个元素,而对于其中的每个元素x,若x大于当前的堆顶元素,则将堆顶元素替换为x,并自堆顶至向下调整堆;这样,在读取完所有元素后,堆中的K个元素即为这N个数中的前K个最大元素,同时堆顶元素为这N个数中的第K大元素。

 

posted @ 2014-11-19 19:00  如果的事  阅读(2128)  评论(0编辑  收藏  举报