数据结构- 堆

来源:互联网 发布:中国货币供给量数据 编辑:程序博客网 时间:2024/05/18 03:35

堆排序是最重要的排序算法之一,在平时的开发以及面试中经常会用到。堆的特点是:

        1,有一颗完全二叉树构成,如图1;

         2,可分为最大堆和最小堆。最大堆的意思就是:任何根节点的数据不小于左右孩子节点的数据;反之,最小堆的意思就是任何节点的数据不大于左右孩子节点的数据;

         3,堆排序的算法复杂度为O(NlgN),比冒泡和插入快,究其原因在于堆只维护局部最大或最小。

         4,堆的存储用数组实现,按层存储,如图1;

                                          

                                                       存储在数组里:

                                                   

                                                         图 1

  下面分四部分讲解堆,分别为:堆的插入,删除,以及堆排序,最后是堆化的优先队列。

  一,堆的插入

  堆的插入只能在最后一个位置插入,如图 2。只能在黑色圈的地方插入。插入以后,必须进行更新,以保持堆的性质,即根数据最大或是最小。更新的思路:从插入节点到根节点,依次比较更新,直到满足条件(本列子就是:根数据小于左右孩子)。

                                                                大致原理如图解:



关键代码如下:

            注意的是:左孩子和右孩子的顺序分别为2i+1,2i+2。结合上面的流程图,插入的过程为:从下往上依次比较,重新调整堆的结构。

[cpp] view plaincopyprint?
  1. void MinHeapFixup(int* a, int i)    
  2. {    
  3.     int j, temp;         
  4.     temp = a[i];    
  5.     j = (i - 1) / 2;      //父结点    
  6.     while (j >= 0 && i != 0)    
  7.     {    
  8.         if (a[j] <= temp)    
  9.             break;    
  10.             
  11.         a[i] = a[j];     //把较大的子结点往下移动,替换它的子结点    
  12.         i = j;    
  13.         j = (i - 1) / 2;    
  14.     }    
  15.     a[i] = temp;    
  16. }    
  17.   
  18.   
  19. void MinHeapAddNumber(int* a, int n, int nNum)    
  20. {    
  21.     a[n] = nNum;    
  22.     MinHeapFixup(a, n);    
  23. }    
二,堆的删除

记住只能删除最顶部的元素,再把最低端的元素放到最顶端,从上往下依次更新整个堆。示意图如下:





[cpp] view plaincopyprint?
  1. void MinHeapFixdown(int a[], int i, int n)    
  2. {    
  3.     int j, temp;    
  4.     
  5.     temp = a[i];    
  6.     j = 2 * i + 1;    
  7.     while (j < n)    
  8.     {    
  9.         if (j + 1 < n && a[j + 1] < a[j]) //如果存在有孩子,比较左右孩子的大小  
  10.             j++;    
  11.     
  12.         if (a[j] >= temp)    
  13.             break;    
  14.     
  15.         a[i] = a[j];       
  16.         i = j;    
  17.         j = 2 * i + 1;    
  18.     }    
  19.     a[i] = temp;    
  20. }    
  21.   
  22. void MinHeapDeleteNumber(int a[], int n)    
  23. {    
  24.     std::swap(a[0], a[n - 1]);    
  25.     MinHeapFixdown(a, 0, n - 1);    
  26. }    

总结:删除,只能删除头结点,然后从上往下依次调整堆;插入,只能在末端插入,然后从下往上依次调整堆。
三,堆化数组,采用插入的思想从下往上依次调整堆即可。此时调整每一个非叶子节点即可。
关键代码如下:
[cpp] view plaincopyprint?
  1. void MakeMinHeap(int* a, int n)    
  2. {    
  3.     for (int i = n / 2 - 1; i >= 0; i--)    
  4.     MinHeapFixdown(a, i, n);    
  5. }    
测试:
结合图形中的数据,我们演示一遍:

[cpp] view plaincopyprint?
  1. int main()  
  2. {  
  3.     int a[8]={9,7,5,6,8,4,10};  
  4.     cout<<"原始数组里的数据为:";  
  5.     for(int i=0;i<7;++i)  
  6.         cout<<a[i]<<" ";  
  7.     cout<<endl<<"堆化后的数组为:";  
  8.     MakeMinHeap(a, 7);  
  9.     for(int i=0;i<7;++i)  
  10.         cout<<a[i]<<" ";  
  11.     cout<<endl<<"增加一个数据3后的数组为:";  
  12.     MinHeapAddNumber(a,7,3);  
  13.     for(int i=0;i<8;++i)  
  14.         cout<<a[i]<<" ";  
  15.       
  16.     MinHeapDeleteNumber(a,8);  
  17.    cout<<endl<<"删除顶点后的数据为:";  
  18.   
  19.    for(int i=0;i<7;++i)  
  20.         cout<<a[i]<<" ";  
  21.     return 0;  
  22. }  




我们自然要问:维护一个堆结构的意义何在?
至少有两个目的:堆排序和优先队列。
四 ,堆排序。思想:既然我们可以每次取出顶点的数据,取到的数据又是最小数据,根据此思想,我们可以把数组里的数据全部取完后得到的数据就是有顺序的了。不过跟归并排序一样,空间复杂度增加了,必须有一个数组去接受。
测试:
[cpp] view plaincopyprint?
  1. int b[8];  
  2.     for(int i=0;i<8;++i)  
  3.     {  
  4.         b[i]=a[0];  
  5.         MinHeapDeleteNumber(a,8-i);  
  6.     }  
  7.     for(int i=0;i<8;++i)  
  8.         cout<<b[i]<<" ";  


我们知道,任何算法都得用时间复杂度计算一下,否则,算法没什么意义。由于按树堆化,树高lgN,最坏情况下,每层计算N次,所以复杂度为:O(NlgN),跟归并一样。
最优队列:最优队列是每次都弹出数据的最大或是最小值。每次进队列后,都要一次更新操作。每次出队列都要一次删除操作。

0 0
原创粉丝点击