max_heap與min_heap


1. 基本概念

max_heap,min_heap是一顆堆樹。其定義如下:

(1) 堆樹是一顆完全二叉樹;

(2) 根節點的值大於子節點(max_heap);對於·min_heap,根節點的值小於子節點;

(3) 左右子樹也是一顆堆樹。

比如下面的完全二叉樹,就是一個max_heap:

                                                     

 

  回想完全二叉樹的性質(一個節點編號為i,如果其左右子樹和父節點存在,那么左子樹編號2i,右子樹編號2i+1,父節點編號i/2),可以用數組來存儲完全二叉樹。令數組a[0]為空,從a[1]開始存儲,那么就是一個完全二叉樹。因此,max_heap或min_heap的底層存儲結構就是一個數組(數組從編號1開始存儲),下面的講解都將以max_heap為例。對於一個max_heap,主要的操作有四個:make_heap(構造一個max_heap),

push_heap(向二叉樹添加節點),pop_heap(取最大值,即根節點),sort_heap(對一個max_heap排序)。

2. 基本操作

2.1 push_heap

  將一個數放入max_heap采用一種上溯(percolate up)的方法。首先將數存到max_heap數組末尾,然后與其父節點比較,如果大於父節點則與其交換,此時完成一層上溯;然后繼續與父節點比較,繼續上溯,直到小於父節點的值。以下圖的max_heap為例:

                                      

設存儲max_heap數組的名字為max_heap

(a) 在原max_heap[11]中插入50;

(b) max_heap[11]與max_heap[11/2]比較,大於父節點,交換;

(c) max_heap[11/2]即max_heap[5]繼續與max_heap[5/2]比較,大於父節點,交換;

(d) max_heap[2]與max_heap[2/2]比較,小於父節點,停止,push_heap成功 。

2.2 pop_heap

  將最大元素即max_heap首元素刪除,采用下溯(percolate down)的方法。首先將數組首元素與尾元素交換,相當於尾元素變成了根節點。然后根節點與其左右子節點比較,將其與大於它的最大子節點交換位置,下溯一層;然后重復以上操作。以下圖的max_heap為例:

                                                

設存儲max_heap數組的名字為max_heap

(a) 將根節點max_heap[1]與max_heap[max_heap.size()-1]交換,即68和24交換;

(b) 此時24在根節點,與其子節點比較,與較大的子節點交換位置,即24(max_heap[1])和65(max_heap[1*2+1])交換;

(c) 24接着與其子節點比較並與大於它的最大子節點交換,即24與32交換;

(d) 此時24沒有子節點,交換完成。

注:通過pop_heap之后,最大根節點到了數組末尾,此時刪除或取值都行。

2.3 sort_heap

由於pop_heap可以將最大節點放置到數組末尾,所以對一個max_heap一直執行pop_heap操作,就能得到一個遞增的排序后的數組。

2.4 make_heap

make_heap給定一個數組,構造max_heap,主要步驟如下:

假設給定數組元素個數為n

(1) 構造完全二叉樹,即從max_heap[1]開始存儲數組元素;

(2) 從第一個有子節點的數組出發(序號為n/2),將其構在為max_heap(將其與最大子節點交換位置);

(3) 從n/2開始往前遍歷(for i=n/2;i>0;i++),對每個節點,都進行max_heap的構造(即對當前節點采用percolate down的方法)。

以數組a=[ 20,12,35,15,10,80,30,17,2,1 ]為例,其完全二叉樹構造如下圖:

                                                   

 

(a) 從5開始,由於a[5]>a[10]已經是max_heap; 再看a[4],由於a[4]<a[8],交換a[4],a[8],此時以a[4]為根節點的子樹已經是max_heap。經過以上兩步,新的樹如圖b)。

(b) 再看a[3],由於a[3]<a[6],交換a[3],a[6]; 再看a[2],由於a[2]<a[4],交換a[2],a[4]。經過以上兩步,新的樹如圖c)。

(c) 再看a[1],由於a[1]<a[3],交換a[1],a[3],此時遍歷完成,最終的max_heap如圖d)所示。

3. 算法實現

 根據2中的算法思想,編寫代碼實現以上四個函數。C++實現如下:

  1 template<class T>
  2 
  3 //首先調用makeHeap,將數據成員heap變成最大堆,才能接着使用popHeap,pushHeap
  4 //sortHeap將輸入的最大堆變成遞增輸出
  5 class MaxHeap
  6 {
  7 public:
  8     MaxHeap();
  9     vector<T>& getMaxHeap()
 10     {
 11         return heap;
 12     }
 13     void printHeap()   //訪問heap數據成員
 14     {
 15         if (heap.size() < 1) return;
 16         for (int i = 1; i < heap.size(); i++)
 17             cout << heap[i] << "  ";
 18         cout << endl;
 19     }
 20 
 21     void pushHeap(T value);
 22     T popHeap();  //返回最大元素,並刪除最大元素
 23     void sortHeap(vector<T>& oldHeap);
 24     void makeHeap(vector<T> vec);
 25 private:
 26     vector<T> heap;
 27     int maxIdx; //最后一個元素的下標
 28 };
 29 
 30 template<class T>
 31 MaxHeap<T>::MaxHeap()
 32 {
 33 }
 34 
 35 template<class T>
 36 void MaxHeap<T>::makeHeap(vector<T> vec)
 37 {
 38     int n = vec.size();
 39     heap.resize(n + 1);
 40     maxIdx = n;
 41     for (int i = 1; i < n + 1; i++)
 42         heap[i] = vec[i - 1];
 43     for (int i = maxIdx / 2; i > 0; i--)
 44     {
 45         int parentIdx = i; //父節點下標
 46         int childIdx = 2 * i; //孩子節點的下標
 47         while (childIdx <= maxIdx) {
 48             if (childIdx + 1 <= maxIdx && heap[childIdx + 1] >= heap[childIdx] && heap[childIdx + 1] > heap[parentIdx]) //右孩子最大
 49             {
 50                 swap(heap[parentIdx], heap[childIdx + 1]);
 51                 parentIdx = childIdx + 1;
 52             }
 53             else if (heap[childIdx] > heap[parentIdx]) //左孩子最大
 54             {
 55                 swap(heap[parentIdx], heap[childIdx]);
 56                 parentIdx = childIdx;
 57             }
 58             else
 59                 break;
 60             childIdx = 2 * parentIdx;
 61         }
 62     }
 63 }
 64 
 65 template<class T>
 66 void MaxHeap<T>::pushHeap(T value)
 67 {
 68     heap.push_back(value); //先構造完全二叉樹
 69     maxIdx++;
 70     int i = maxIdx;
 71     while (i!=1 && heap[i] > heap[i / 2]) {
 72         swap(heap[i], heap[i / 2]);
 73         i = i / 2;
 74     }
 75 }
 76 
 77 template<class T>
 78 T MaxHeap<T>::popHeap()
 79 {
 80     if (maxIdx == 0) {
 81         cout << "empty MaxHeap" << endl;
 82     }
 83     swap(heap[1], heap[maxIdx]);
 84     int i = 1;
 85     int ci = 2 * i; //左孩子節點
 86     while (ci<maxIdx) {
 87         if (ci + 1 < maxIdx && heap[ci] < heap[ci + 1] && heap[i] < heap[ci + 1]) //右孩子最大
 88         {
 89             swap(heap[i], heap[ci + 1]);
 90             i = ci + 1;
 91         }
 92         else if (heap[ci] > heap[i]) //左孩子最大
 93         {
 94             swap(heap[i], heap[ci]);
 95             i = ci;
 96         }
 97         else
 98             break;
 99         ci = 2 * i;
100     }
101     T tmp = heap.back();
102     heap.pop_back();
103     maxIdx--;
104     return tmp;
105 }
106 
107 template<class T>
108 void MaxHeap<T>::sortHeap(vector<T>& oldheap) //傳入參數必須是maxHeap
109 {
110     oldheap.insert(oldheap.begin(), oldheap[0]); //0為填充
111     int n = size(oldheap)-1;
112     int endIdx = n;
113     for (int k = 1; k < n; k++)
114     {
115         //此段代碼與pop_heap基本一致
116         swap(oldheap[1], oldheap[endIdx]);
117         int i = 1;
118         int ci = 2 * i; //左孩子節點
119         while (ci < endIdx) {
120             if (ci + 1 < endIdx && oldheap[ci] < oldheap[ci + 1] && oldheap[i] < oldheap[ci + 1]) //右孩子最大
121             {
122                 swap(oldheap[i], oldheap[ci + 1]);
123                 i = ci + 1;
124             }
125             else if (oldheap[ci] > oldheap[i]) //左孩子最大
126             {
127                 swap(oldheap[i], oldheap[ci]);
128                 i = ci;
129             }
130             else
131                 break;
132             ci = 2 * i;
133         }
134         endIdx--; //每次排序后,最后一個元素就是最大,所以無序數組的下標要減少
135     }
136     oldheap.erase(oldheap.begin()); //去掉填充位
137 }

為了驗證以上代碼合理性,利用STL自帶的make_heap,sort_heap,pop_heap,push_heap進行對照:

 1 int main()
 2 {
 3     vector<int> heap1 = { 0,1,2,3,4,8,9,3,5 };
 4     make_heap(heap1.begin(), heap1.end()); //STL自帶的
 5     for (int i = 0; i < size(heap1); i++)
 6         cout << heap1[i] << "  ";
 7     cout << endl;
 8 
 9     vector<int> vec = { 0,1,2,3,4,8,9,3,5 };
10     MaxHeap<int> heap2;
11     heap2.makeHeap(vec);
12     heap2.printHeap();
13 
14     
15     heap1.push_back(7);
16     push_heap(heap1.begin(), heap1.end());
17     for (int i = 0; i < size(heap1); i++)
18         cout << heap1[i] << "  ";
19     cout << endl;
20 
21     heap2.pushHeap(7);
22     heap2.printHeap();
23 
24 
25     pop_heap(heap1.begin(), heap1.end());
26     cout << heap1.back()<<endl; //自帶的庫函數不會刪除最大的元素
27     heap1.pop_back();
28     for (int i = 0; i < size(heap1); i++)
29         cout << heap1[i] << "  ";
30     cout << endl;
31 
32     cout << heap2.popHeap() << endl;
33     heap2.printHeap();
34 
35 
36     vector<int> tmpheap = heap1;
37     sort_heap(heap1.begin(), heap1.end());
38     for (int i = 0; i < size(heap1); i++)
39         cout << heap1[i] << "  ";
40     cout << endl;
41 
42     heap2.sortHeap(tmpheap);
43     for (int i = 0; i < size(tmpheap); i++)
44         cout << tmpheap[i] << "  ";
45     cout << endl;
46     
47 
48     system("pause");
49 }

PS: STL的最大堆函數需要包含頭文件#include <algorithm>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM