STL之heap

heap并不归属与STL容器组件,它是个幕后英雄,扮演priority queue的助手。
heap在algorithm头文件中实现,sgi把它放在了stl_heap.h中。
共有4个操纵heap的函数,分别是:

push_heap, pop_heap, make_heap, sort_heap

堆的知识

堆的定义

二叉堆是一种完全二叉树,它满足两个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆(max-heap)。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆(min-heap)。

以下均以max-heap为例。

堆的存储

一般都用数组来表示堆:

如果数组从0节点开始存储,i节点的父节点下标就为(i–1)/2,它的左右子节点下标分别为2*i+1和2*i+2。
如果数组的0节点弃用,从1节点开始存储,i节点的父节点下标为i/2,左右子节点下标分别为2*i和2*i+1。

堆的push

新加入的元素放在数组的末尾处,然后执行一个上溯过程(max-heap):将新节点与其父节点比较,如果比父节点大,父子就兑换位置,如此一直上溯,直到不需要兑换或到根节点。

堆的pop

取出根节点,将最后一个节点的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。
调整时先在左右儿子结点中找最大的,如果父结点比这个最大的子结点还大说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程。

堆化数组

最简单的一个想法是:一个一个元素的push进heap进行了。但实际上可以更高效一些,看上面的那张图,所有的叶子节点已经是合法的堆了,所以只要从56开始调整就行了。

这三个操作分别对应heap的三个函数,至于最后一个sort_heap(),只要不断地执行pop操作就可以了。

参考文献

白话经典算法系列之七 堆与堆排序
http://blog.csdn.net/morewindows/article/details/6709644/
STL源码剖析,侯捷

我的实现

根据以上思路写出的代码(可能用了两个小时):

#include <iostream>
#include <ctime>
#include <cstdlib>
#include <vector>
#include <memory>
using namespace std;

int buf[] = {34, 56, 43, 82};
vector<int> heap(buf, buf+4);

// 调整节点i, 使其符合max-heap
// 假定节点i以后的节点都满足max-heap
void adjust(int i)
{
    int j = 2*i+2; // 右子节点
    // 不断下沉
    while(j < heap.size()) // 右子节点存在
    {
        if(heap[j-1]>heap[j]) --j; // 找到子节点中较大的
        if(heap[j]<=heap[i]) break;
        swap(heap[j],heap[i]);
        i = j;
        j = 2*i+2;
    }
    if(j == heap.size()) // 只有左子节点, 没有右子节点的情况
    {
        if(heap[j-1]>heap[i]) swap(heap[j-1],heap[i]);
    }
}

// 构建max-heap
void MakeHeap()
{
    int len = heap.size();
    // 共有(len+1)/2个叶子节点
    // 从len-叶子节点数+1开始构造堆, 即(len+1)/2开始
    for(int i=(len+1)/2; i>=0; --i)
        adjust(i);
}

void PushHeap(int a)
{
    heap.push_back(a);
    int j = heap.size()-1; // 子节点
    int i; // 父节点
    // 上溯
    while(j)
    {
        i = (j-1)/2;
        if(heap[j] <= heap[i]) break;
        swap(heap[j],heap[i]);
        j = i;
    }
}

void PopHeap()
{
    cout << *heap.begin() << ' ';
    *heap.begin() = *(heap.end()-1);
    heap.pop_back();
    adjust(0);
}

int main()
{
    MakeHeap();
    for(int i=0; i<10; ++i)
        PushHeap(rand()%99);
    for(int i=0; i<14; ++i)
        PopHeap();
    return 0;
}

STL源码

下面该是看STL有关heap的四个函数源码的时候了。
下面是这四个函数的声明:

template <class RandomAccessIterator>
void make_heap (RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
void make_heap (RandomAccessIterator first, RandomAccessIterator last, Compare comp );

template <class RandomAccessIterator>
void push_heap (RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
void push_heap (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

template <class RandomAccessIterator>
void pop_heap (RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
void pop_heap (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

template <class RandomAccessIterator>
void sort_heap (RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
void sort_heap (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

每个函数都有两个版本,第一个版本接受两个随机访问迭代器,第二个版本额外还接受一个比较对象。
以下是第一个版本的源码:

template <class _RandomAccessIterator, class _Distance, class _Tp>
void
__push_heap(_RandomAccessIterator __first,
            _Distance __holeIndex, _Distance __topIndex, _Tp __value)
{
    _Distance __parent = (__holeIndex - 1) / 2;
    while (__holeIndex > __topIndex && *(__first + __parent) < __value)
    {
        *(__first + __holeIndex) = *(__first + __parent);
        __holeIndex = __parent;
        __parent = (__holeIndex - 1) / 2;
    }
    *(__first + __holeIndex) = __value;
}

template <class _RandomAccessIterator, class _Distance, class _Tp>
inline void
__push_heap_aux(_RandomAccessIterator __first,
                _RandomAccessIterator __last, _Distance*, _Tp*)
{
    __push_heap(__first, _Distance((__last - __first) - 1), _Distance(0),
                _Tp(*(__last - 1)));
}

template <class _RandomAccessIterator>
inline void
push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
    // 调用此函数时, 新元素应已置于容器的最尾端
    __push_heap_aux(__first, __last, __distance_type(__first), __value_type(__first));
}


template <class _RandomAccessIterator, class _Distance, class _Tp>
void
__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,
              _Distance __len, _Tp __value)
{
    _Distance __topIndex = __holeIndex;
    _Distance __secondChild = 2 * __holeIndex + 2;
    while (__secondChild < __len)
    {
        if (*(__first + __secondChild) < *(__first + (__secondChild - 1)))
            __secondChild--;
        *(__first + __holeIndex) = *(__first + __secondChild);
        __holeIndex = __secondChild;
        __secondChild = 2 * (__secondChild + 1);
    }
    if (__secondChild == __len)
    {
        *(__first + __holeIndex) = *(__first + (__secondChild - 1));
        __holeIndex = __secondChild - 1;
    }
    __push_heap(__first, __holeIndex, __topIndex, __value);
}

template <class _RandomAccessIterator, class _Tp, class _Distance>
inline void
__pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
           _RandomAccessIterator __result, _Tp __value, _Distance*)
{
    *__result = *__first;
    __adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value);
}

template <class _RandomAccessIterator, class _Tp>
inline void
__pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last,
               _Tp*)
{
    __pop_heap(__first, __last - 1, __last - 1,
               _Tp(*(__last - 1)), __distance_type(__first));
}

template <class _RandomAccessIterator>
inline void pop_heap(_RandomAccessIterator __first,
                     _RandomAccessIterator __last)
{
    __pop_heap_aux(__first, __last, __value_type(__first));
}


template <class _RandomAccessIterator, class _Tp, class _Distance>
void
__make_heap(_RandomAccessIterator __first,
            _RandomAccessIterator __last, _Tp*, _Distance*)
{
    if (__last - __first < 2) return;
    _Distance __len = __last - __first;
    _Distance __parent = (__len - 2)/2;

    while (true)
    {
        __adjust_heap(__first, __parent, __len, _Tp(*(__first + __parent)));
        if (__parent == 0) return;
        __parent--;
    }
}

template <class _RandomAccessIterator>
inline void
make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
    __make_heap(__first, __last,
                __value_type(__first), __distance_type(__first));
}


template <class _RandomAccessIterator>
void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
    while (__last - __first > 1)
        pop_heap(__first, __last--);
}

第二个版本与第一个版本只有比较的地方不同,其他地方完全一样,例如:

    while (__holeIndex > __topIndex && *(__first + __parent) < __value)
    {
        *(__first + __holeIndex) = *(__first + __parent);
        __holeIndex = __parent;
        __parent = (__holeIndex - 1) / 2;
    }

    while (__holeIndex > __topIndex && __comp(*(__first + __parent), __value))
    {
        *(__first + __holeIndex) = *(__first + __parent);
        __holeIndex = __parent;
        __parent = (__holeIndex - 1) / 2;
    }

通过设置第二个版本的第三个模板参数,就可以实现min-heap。

总体而言,sgi的实现和我的实现大体相同,还有几点不同:
1. push和pop操作的元素的位置
调用push()之前,新元素应已置于容器的最尾端,
调用pop()之后,旧元素将置于容器的最尾端;
而我的push值是直接作为参数传入的,pop值直接输出了。
所以,调用sort_heap()之后,两个迭代器之间的数据就有序了。
2. 交换动作
push()和pop()没有在循环中执行swap动作,而是将交换留到了最后进行,这样也就没办法在中途堆已合法的情况下退出了。
我觉得各有利弊吧,如果堆非常大,可能sgi就需要循环比较多次。不过毕竟人家是资深的,可能效果比我的好。

你可能感兴趣的:(heap)