听说有一种最小(大)堆,不限于是完全二叉树,而是完全D叉树,名为D-ary Heap(http://en.wikipedia.org/wiki/D-ary_heap)。D可以是1,2,3,4,100,对于优先队列该有的功能都没有问题。
动手写一个D-ary Heap,应该不难。简单起见,不考虑像STL一样通过template传入Comp类,下面的实现要求T类型重载了operator <和operator >。
template<class T> class DaryHeap { size_t D; size_t size; vector<T> a; ... };
用vector容器存储数据,是因为在优先队列的插入、构建、删除操作需要RandomAccessIterator,而使用deque或其他STL容器都将影响性能。
为了节约点访问vector::size()函数的代价,用size_t size记录堆的大小。这也许没有必要,因为编译器可能对size()的调用做优化,而且访问size()的次数也并不多。
然后写堆的基本操作,即元素的上移下移。完全D叉树的性质和二叉树差不多,甚至可以推广到1叉树(数组)。
void up(size_t i) { while(i) { size_t p = (i-1)/D; if(a[p]>a[i]) { swap(a[p], a[i]); i = p; } else break; } } void down(size_t i) { size_t ii; size_t min = i*D+1; while(min<size) { for(ii=i*D+2; ii<size; ii++) if(a[min] > a[ii]) min = ii; if(a[min]<a[i]) { swap(a[min], a[i]); i = min; min = i*D+1; } else break; } }
写好了这两个函数,堆的插入删除排序构建就都一样了:
DaryHeap(int _D=2):size(0) { if(_D<1) D=1; else D=_D; } size_t getsize(){return size;} void push(T& x) { a.push_back(x); up(size++); } T& top() { if(size) return a[0]; } void pop() { if(size) { a[0] = a[--size]; a.pop_back(); down(0); } }
最后测试:
main() { DaryHeap<int> h(10); for(int i=10; i>0; i--) { h.push(i); // h.print(); } for(int i=10; i>0; i--) { cout <<h.top()<<ends; h.pop(); } }
D-ary Heap的定义是:
1 逻辑上是一个完全多叉树;
2 每个父节点有最多D个子节点;
3 子节点永远比父节点大(小),前提是节点具有可比性。
性质:
1 父节点=(子节点-1)/D;
2 子节点=父节点*D+i (i=1, 2, ..., D);
操作:
1 插入时将新插入元素放在最尾端,并与父节点交换位置直到比父节点大,即up操作。
2 从一个数组a构建成一个D-ary Heap,即需要每一个a[i](i>1)做如上的up操作。
3 删除一个节点时,将尾部的节点复制到待删除节点的位置,size-=1,然后将新复制节点从待删除节点开始向下移动,直到所有的子节点都比新复制节点大,down操作。至于怎么求子节点中最小的那个,要么去构建个局部的最小堆,要么就只好遍历咯。
4 排序:每一次将堆的根节点与尾节点a[size-1]交换,同时size-=1,再调整新根节点的位置(就是将堆首删除后拿到数组尾端。)
总的来说,和熟为人知的二叉树除了up和down有一点点不同,其他的一模一样。