数据结构练手05 关于堆的up策略和down策略实现

堆,是一个相当重要的数据结构,它是优先队列,堆排序,Dijkstra等算法的实现保证!

堆的主要特性是:

1、根结点是最大/最小的,而这个主要的区别,就是实现比较操作时是less or greater, 因此可以使用纯虚化 比较接口,把实现放到子类。 附: STL中采用的是模板默认参数的方法实现。

2、需要两个表示大小的变量来标定堆or数组的大小。因为pop操作因让堆的有效长度变小,而数组的长度不变。

3、堆的插入操作一般是插入到数组的末尾,这里最好用vector, 因为它可以在常数时间内尾插入数据且能够动态生长。

 1 #include <vector>
 2 #include <ostream>
 3 using namespace std;
 4 template<class T>
 5 class HEAP{
 6 protected:
 7     vector<T> elements;
 8     int totalSize;
 9 public:
10     HEAP() : totalSize(0),elements(){}
11     virtual ~HEAP(){}
12     int left(int index){return 2*index+1;}
13     int right(int index){return 2*index+2;}
14     int parent(int index){return (index-1)/2;}
15     
16     void upHeapity(int index);
17     void downheapity(int index);
18     
19     void makeHeap_up();
20     void makeHeap_down();
21     
22     T& pop_up();
23     T& pop_down();
24 
25     void insert_up(const T& x);
26     void insert_down(const T& x);
27     void swap(T& a, T& b){int tmp=a; a=b; b=tmp;}
28     void show(ostream& os) const;
29     void sort();
30 
31     virtual bool comp(int a, int b)=0;
32 };
类定义
 1 template<class T>
 2 class maxHeap : public HEAP<T>{
 3 public:
 4     maxHeap() : HEAP<T>(){}
 5     ~maxHeap(){};
 6     bool comp(int a, int b) { return elements[a]>elements[b]? true:false;}
 7 };
 8 
 9 template<class T>
10 class minHeap : public HEAP<T>{
11 public:
12     minHeap() : HEAP<T>(){}
13     ~minHeap(){};
14     bool comp(int a, int b) { return elements[a]<elements[b]? true: false;}
15 };
子类定义

要保证这个heapity特性,(假定是讨论最大堆)一般有两种方式:从根到叶(down) 和 从叶到根(up),这两种实现各有优缺点。我们先说下各种操作的文字表述:

<<算法导论>>中采用了down的策略,即比较本结点,左右结点,得到最大值的下标索引,若不是最大索引不是本结点,则交换本结点和最大索引,接着用最大索引实现递归操作。downHeapity保证了该子树满足堆的特性。

 1 template<class T>
 2 void HEAP<T>::downheapity(int index)
 3 {
 4     int l = left (index);
 5     int r = right(index);
 6     int large = index;
 7     if(l<totalSize)
 8         if(comp(l,index))
 9             large = l;
10         else
11             large = index;
12     if(r<totalSize)
13         if(comp(r,large))
14             large = r;
15     if(index != large){
16         swap(elements[index], elements[large]);
17         downheapity(large);
18     }
19 }
downheapity策略

StL中采用了是up的策略,即新增加结点时,仅需层层比较和父节点的大小,最多到根节点。up策略满足了从该叶到根的路径满足了堆的特性

1 template<typename T>
2 void HEAP<T>::upHeapity(int index)
3 {
4     while((index!=0) && (comp(index, parent(index)))){
5         swap(elements[index], elements[parent(index)]);
6         index = parent(index);
7     }
8 }
upheapity策略

另外,在建堆操作中,若采用down策略,则可以从index= lenArray/2 的位置进行downHeapity(index)一直递减到根结点就行。

1 template<class T>
2 void HEAP<T>::makeHeap_down()
3 {
4     totalSize = elements.size();          // 很重要,重新调整堆大小
5     for(int i=totalSize>>1; i>=0; --i){   // 从一半到零
6         downheapity(i);
7     }
8 }
建堆的down策略

而采用up策略,则从index = lenArray-1的位置递减到 lenArray/2的位置,中间一直调用upHeapity(index)就行。

1 template<class T>
2 void HEAP<T>::makeHeap_up()
3 {
4     totalSize = elements.size();
5     for (int i=elements.size()-1; i>elements.size()/2; --i) {
6         upHeapity(i);
7     }
8 }
建堆的up策略

我们对堆进行pop操作时,一般来说要保留根结点的值用于最后的返回。实现中我们还需要将根结点和尾结点的值进行交换。这里继续说下down策略和up策略的做法。

down策略时,由于前一步已经交换了根结点和尾结点,且调整了堆大小的长度,这样,我们就可以从索引为堆大小一半的位置开始,递减到根,一直调用 downheapity(index)就行。

 1 template<class T>
 2 T& HEAP<T>::pop_down()
 3 {
 4     T rval = elements[0];
 5     swap(elements[0],elements[totalSize-1]);
 6     totalSize--;
 7     for(int i=totalSize/2; i>=0; i--){
 8         downheapity(i);
 9     }
10     return rval;
11 }
pop操作的down策略

up策略是,就比较麻烦点了,需要将交换后的根结点下放到某个小值的位置,然后再调用次upheapity保证堆特性的满足。这个实现起来比较讨厌,要判断一些边界条件。

 1 template<class T>
 2 T& HEAP<T>::pop_up()
 3 {
 4     T rval = elements[0];
 5     swap(elements[0],elements[totalSize-1]);
 6     totalSize--;
 7     int i=0;
 8     int tmp=i;
 9     while(left(i)<totalSize){
10         if( (right(i)<totalSize) && comp(left(i),right(i)) || (right(i)>=totalSize)){
11             tmp = left(i);
12         }
13         if( (right(i)<totalSize) && (!comp(left(i),right(i)))){
14             tmp = right(i);
15         }
16         swap(elements[i],elements[tmp]);
17         i = tmp;
18     }
19     upHeapity(tmp);
20     return rval;
21 }
pop操作的up策略

而对于堆排序,则就是遍历调用pop n-1次就行。

1 template<class T>
2 void HEAP<T>::sort()
3 {
4     int t = totalSize;
5     for(int i=0; i<t; ++i){
6         pop_up();
7 //        pop_down();
8     }
9 }
堆排序

对于元素的插入来说,那肯定是StL的up占优了。因为up策略就是从尾结点开始向父节点进行比较,最多比较到根节点。 而若采用down策略,则从堆大小一半的位置递减到根,其中一直调用downheapity(index)操作。

 1 template<typename T>
 2 void HEAP<T>::insert_up(const T& x)
 3 {
 4     if(totalSize == elements.size())
 5         elements.push_back(x);
 6     else
 7         elements[totalSize] = x;
 8     totalSize++;
 9     upHeapity(totalSize-1);
10 }
11 
12 template<typename T>
13 void HEAP<T>::insert_down(const T& x)
14 {
15     if(totalSize == elements.size())
16         elements.push_back(x);
17     else
18         elements[totalSize] = x;
19     totalSize++;
20     for(int i=totalSize/2; i>=0; i--){
21         downheapity (i);
22     }
23 }
insert的down策略和up策略

因此,这里我们可以总结下使用堆时候的策略:

除了插入操作使用up策略外,其他所有的操作都使用down策略。调用down策略,都从一半堆大小的位置开始递减到根。

另外,对于边界条件来说,down策略一般从尾结点的父节点开始,及 parent(totalSize-1), 即 (totalSize-1)/2; 由于下标从0开始,边界条件就很让人纠结,我们宁愿多操作一次,及从total/2位置开始,这样也没啥影响。

  • 之前我的总结有问题,今天反思了下,若都从一半堆大小开始递减到根,那么时间复杂度挺大的。其实所有的操作都采取down策略更易理解。
  • 对于从(size-1)/2还是size/2开始,我觉得还是从(size-1)/2更好,这样循环代码可以修改下,且也能避免上面所误解的地方
 1     insert_down 和 pop_down 中的
/*for(int i=totalSize/2; i>=0; i--){ 2 downheapity(i); 3 }*/ 4 改为: 5 int p = parent (totalSize-1); 6 while( p != parent(p)){ 7 downheapity (p); 8 p = parent(p); 9 } 10 downheapity(p);

 

使用堆的时候,一定要注意是采用数组长度还是堆长度。我们可以总结下:仅建堆时采用数组长度,同时堆长度等于数组长度其余操作使用的都是堆长度,同时要注意调整堆长度水位

测试代码:

 1 #include "myHeap.h"
 2 #include <iostream>
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     maxHeap<int> mh;
 8             mh.insert_down(170);
 9     mh.insert_down(20);
10     mh.insert_down(30);
11     mh.insert_down(1);
12     mh.insert_down(7);
13 
14     mh.insert_down(10);
15     mh.insert_down(90);
16 
17     cout << mh << endl;
18     cout << "--------" << endl;
19     mh.sort();
20     cout << mh << endl;
21     mh.makeHeap_up();
22     cout << mh << endl;
23 }
测试代码

posted @ 2013-05-27 23:58  xield  阅读(834)  评论(0编辑  收藏  举报