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>