準確來說,heap並不屬於STL容器,但它是其中一個容器priority queue必不可少的一部分。顧名思義,priority queue就是優先級隊列,容許用戶以任何次序將任何元素加入容器內,但取出時是從優先權最高的元素開始取。而優先權有兩種,能夠是容器內數值最低的,也能夠是數值最高的。而priority queue是選擇了數值高做爲評判優先級的標準。對應實現方法就是binary max heap,其做爲priority queue的底層機制。算法
所謂的binary heap(二叉堆),是一種徹底二叉樹,也就是說,整棵二叉樹除了最底層的葉子節點外,其餘節點都是被填滿的,而最底層的節點是從左至右不得有空節點的,如圖所示:數組
如圖所示,徹底二叉樹整棵樹內沒有任何節點漏洞,這使得咱們可使用數組來存儲一棵徹底二叉樹上的全部結點。另外,若是咱們數組的索引1開始記錄節點,那麼父節點與子節點在數組中的關係就通常爲:父節點在 i 處,其左子節點必位於數組的 2i 處,其右子節點必位於數組的 2i+1 處。這種以數組表示樹的方式,稱爲隱式表述法。咱們的heap須要動態改變節點數,因此用vector是更好的選擇。因爲priority queue選擇的是binary max heap作爲本身的底層機制,因此也只提供了max heap的實現,因此咱們接下里討論的都是大根堆(max heap)。每一個節點的鍵值都大於或等於其子節點的鍵值。dom
爲了知足徹底二叉樹的條件,最新加入的元素必定要放在最下一層作爲葉子節點,並填補在從左至右的第一個空格。新加入的元素並不必定適合於現有的位置,爲了知足max-heap的條件,咱們須要對剛加入的元素進行一個上浮(percolate up)的操做:將新節點與其父節點比較,若是其鍵值比父節點的大,就父子交換位置,交換後新加的元素成爲了父節點,此時再與它的父節點比較,如此一直上溯,直到不需對換或直到根節點爲止。函數
push_heap函數接受兩個隨機迭代器,用來表示一個heap底部容器(vector)的頭尾,而且新元素已經插入到底部容器的最尾端,纔會進入到該函數。若是參數不符合,或並沒有新元素插入,該函數的執行結果不可預期。spa
1 template <class RandomAccessIterator> 2 inline void push_heap(RandomAccessIterator first, RandomAccessIterator last) { 3 // 注意,此函數被調用時,新元素應已置於底層容器的最尾端。 4 //便是新元素被加入到數組(本來最後元素的下一位置)後,才調用該函數 5 __push_heap_aux(first, last, distance_type(first), value_type(first)); 6 }
7 template <class RandomAccessIterator, class Distance, class T> 8 inline void __push_heap_aux(RandomAccessIterator first, 9 RandomAccessIterator last, Distance*, T*) { 10 __push_heap(first, Distance((last - first) - 1), Distance(0), 11 T(*(last - 1))); 12 //於上個函數獲取到的迭代器所指對象類型T,用於強制轉換 13 //Distance(0)爲指出最大值的索引值 14 //Distance((last - first) - 1)爲指出新添值的索引值 15 }
16 template <class RandomAccessIterator, class Distance, class T> 17 void __push_heap(RandomAccessIterator first, Distance holeIndex, 18 Distance topIndex, T value) { 19 //holeIndex爲指出新添值的索引值 20 //topIndex最大值的索引值 21 //value爲新添值內容 22 Distance parent = (holeIndex - 1) / 2; // 找出新元素的父節點 23 while (holeIndex > topIndex && *(first + parent) < value) { 24 // 當還沒有到達頂端,且父節點小於新值(不符合 heap 的次序特性),繼續上浮 25 // 因爲以上使用 operator<,可知 STL heap 是一種 max-heap(大根堆)。 26 *(first + holeIndex) = *(first + parent); // 子位置設父值 27 holeIndex = parent; // percolate up:調整新添值的索引值,向上提高至父節點。 28 parent = (holeIndex - 1) / 2; // 獲取新索引值的父節點 29 } // 持續到頂端,或知足 heap 的次序特性爲止。 30 *(first + holeIndex) = value; // 令最後的索引值爲新值,完成插入。 31 }
最大值在根節點處,pop操做取走根節點(實際上是把它轉移到vector的尾端節點上),爲了知足徹底二叉樹的條件,必須割捨最底層最右邊的節點,把其值拿出來放至一臨時變量裏,而後該位置放根節點的值(最後會被pop_back()給移除)。3d
1 template <class RandomAccessIterator> 2 inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last) { 3 __pop_heap_aux(first, last, value_type(first)); 4 } 5 6 template <class RandomAccessIterator, class T> 7 inline void __pop_heap_aux(RandomAccessIterator first, 8 RandomAccessIterator last, T*) { 9 __pop_heap(first, last - 1, last - 1, T(*(last - 1)), distance_type(first)); 10 // 設定準條調整的值爲尾值,而後將首值調至 11 // 尾節點(因此以上將迭代器 result 設爲 last-1)。而後重整 [first, last-1), 12 // 使之從新成一個合格的 heap。 13 } 14 15 template <class RandomAccessIterator, class T, class Distance> 16 inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last, 17 RandomAccessIterator result, T value, Distance*) { 18 *result = *first; // 設定尾值爲首值,因而尾值即爲要求的結果, 19 // 可由客端稍後再調用 pop_back() 取出尾值。 20 __adjust_heap(first, Distance(0), Distance(last - first), value); 21 // 以上從新調整 heap,要調整的索引值爲 0(亦即樹根處),欲調整值爲 value(原尾值)。 22 } 23 24 template <class RandomAccessIterator, class Distance, class T> 25 void __adjust_heap(RandomAccessIterator first, Distance holeIndex, 26 Distance len, T value) { 27 //holeIndex:要調整的索引值,從樹根處出發 28 //len:全部元素個數(不包括被調整到尾節點的首節點),即[0, len) 29 //value:記錄了原最底層最右邊的節點的值 30 Distance topIndex = holeIndex; 31 Distance secondChild = 2 * holeIndex + 2; // 調整的索引值的右子節點 32 while (secondChild < len) { 33 // 比較這左右兩個子值,而後以 secondChild 表明較大子節點。 34 if (*(first + secondChild) < *(first + (secondChild - 1))) 35 secondChild--; 36 // Percolate down:令較大子值代替要調整索引值處的值,再令調整的索引值下移至較大子節點處。 37 *(first + holeIndex) = *(first + secondChild); 38 holeIndex = secondChild; 39 // 繼續找出要調整的索引值的右子節點 40 secondChild = 2 * (secondChild + 1); 41 } 42 if (secondChild == len) { // 相等,說明沒有右子節點(不是說沒有,而是被準備pop掉的元素佔用了,見*result = *first;),只有左子節點 43 // Percolate down:令左子值代替要調整索引值處的值,再令要調整的索引值下移至左子節點處。 44 *(first + holeIndex) = *(first + (secondChild - 1)); 45 holeIndex = secondChild - 1; 46 } 47 48 // 此時可能還沒有知足次序特性,再執行一次上浮操做 49 __push_heap(first, holeIndex, topIndex, value); 50 }
注意,pop_heap以後,最大元素只是被放置於底部容器的最尾端,還沒有被取走。若是要取其值,可以使用底部容器的back()函數。若是要移除它,可以使用底部容器所提供的pop_back()函數。code
既然每次調用pop_heap可得到heap中鍵值最大的元素,若是持續對整個heap作pop_heap操做,且每次的操做範圍都從後向前縮減一個元素,那麼當整個程序執行完畢,咱們便有了一個遞增序列。sort_heap即是如此作的。該函數接受兩個迭代器,用來表示heap的首尾。若是並不是首尾,該函數的執行結果不可預期。注意,排序事後,底層容器裏的就再也不是一個合法的heap了。對象
1 template <class RandomAccessIterator> 2 void sort_heap(RandomAccessIterator first, RandomAccessIterator last) { 3 // 如下,每執行一次 pop_heap(),極大值即被放在尾端。 4 // 尾端自減後(往左移動一格)再執行一次 pop_heap(),次極值又被放在新尾端。一直下去,最後即得 5 // 排序結果。 6 while (last - first > 1) 7 pop_heap(first, last--); // 每執行 pop_heap() 一次,操做範圍即退縮一格。 8 }
這個算法用來將一段現有的數據轉化爲一個heap。把一段數據轉換爲heap的要點就是找到最後一個擁有子節點的節點,例如:blog
假設這是一個普通數組,並沒有heap特性,那麼要想其轉化爲一個heap,切入點就是找到最後一個擁有子節點的節點,上圖而言就是E點,以E節點爲首的子樹,對該子樹進行下沉操做和上浮操做,即先交換E跟J的值,再對J進行上浮操做。這樣以E節點爲首的子樹就符合heap特性了。而後自減,來到了D節點(倒二擁有子節點的節點),一樣對以D節點爲首的子樹進行下沉和上浮操做;而後再自減,直至到達根節點爲止。這樣整個數組就符合heap特性了。那麼問題來了?怎麼在一個普通數組中找到最後一個擁有子節點的節點的索引值呢?能夠證實的是,若是有一長度爲n的數組,那麼最後一個擁有子節點的節點的索引值就是 (n - 2) / 2 ,這是數組從索引0開始的狀況。排序
1 // 將 [first,last) 排列爲一個 heap。 2 template <class RandomAccessIterator> 3 inline void make_heap(RandomAccessIterator first, RandomAccessIterator last) { 4 __make_heap(first, last, value_type(first), distance_type(first)); 5 } 6 7 template <class RandomAccessIterator, class T, class Distance> 8 void __make_heap(RandomAccessIterator first, RandomAccessIterator last, T*, 9 Distance*) { 10 if (last - first < 2) return; // 若是長度為 0 或 1,沒必要從新排列。 11 Distance len = last - first; 12 // 找出第一個須要重排的子樹頭部,以 parent 標示出。因爲任何葉子節點都不需執行 13 // perlocate down(下沉),因此有如下計算。 14 Distance parent = (len - 2) / 2; 15 16 while (true) { 17 // 重排以 parent 爲首的子樹。len 是爲了讓 __adjust_heap() 判斷操做範圍 18 __adjust_heap(first, parent, len, T(*(first + parent))); 19 if (parent == 0) return; // 直至根節點,就結束。 20 parent--; // 未到根節點,就將(即將重排的子樹的)索引值向前一個節點 21 } 22 }
heap沒有迭代器,heap的全部元素都必須遵循特別的排列規則,因此heap不提供遍歷功能,也不提供迭代器。