STL——heap結構及算法


heap(隱式表述,implicit representation)

1. heap概述 : vector + heap算法

heap並不歸屬於STL容器組件,它是個幕后英雄,扮演priority queue的助手。顧名思義,priority queue允許用戶以任何次序將任何元素推入容器內,但取出時一定是從優先權最高(也就是數值最高)的元素開始取。binary max heap 正是具有這樣的特性,適合作為priority queue 的底層機制。

讓我們做一點分析。如果使用list 作為priority queue的底層機制,元素出入操作可享常數時間。但是要找到list中的極值,卻需要對整個list進行線性掃描。我們也可以改變做法,讓元素插入前先經過排序,使得list的元素值總是由小到大(或由大到小),但這么一來,收之東隅卻失之桑榆:雖然取得極值以及元素刪除操作達到最高效率,可元素的插入卻只有線性表現。

比較好的做法是以binary search tree 作為priority queue的底層機制。這么一來,元素的插入和極值的取得就有O(logN) 的表現。但殺雞用牛刀,未免小題大做,一來binary search tree 的輸入需要足夠的隨機性,二來binary search tree並不容易實現。priority queue 的復雜度,最好介於queue 和 binary search tree 之間,才算適得其所。bianry heap便是這種條件下的適當候選人。

所謂binary heap 就是一種complete binary tree(完全二叉樹),也就是說,整棵binary tree 除了最底層的葉節點之外,是填滿的,而最底層的葉節點(s)由左至右又不得有空隙。

complete binary tree 整棵樹內沒有任何節點漏洞,這帶來一個極大的好處:我們可以利用array來存儲所有節點。假設動用一個小技巧,將array的#0元素保留(或設為無限大或無限小),那么當complete binary tree中的某個節點位於array的i處時,其左子節點必位於array的2i處,其右子節點比位於array的2i+1處,其父節點必位於“i/2”處。通過這么簡單的位置規則,array可以輕易實現出complete binary tree。這種以array表述tree的方式,我們稱為隱式表述法。

這么一來,我們需要的工具就很簡單了:一個array 和 一組 heap算法(用來進行元素操作,並將某一整組數據排列成一個heap)。array的缺點是無法動態改變大小,而heap卻需要這項功能,因此,以vector代替array是更好的選擇。

根據元素的排列方式,heap可分為max-heap 和 min-heap兩種,前者每個節點的鍵值(key)都大於或等於其子節點鍵值,后者的每個節點鍵值都小於或等於其子節點鍵值。STL提供的是max-heap。

2. heap算法

2.1 push_heap算法:上溯

percolate_up(上溯)程序:將新節點拿來與其父節點比較,如果其鍵值(key)比父節點大,就父子對換位置。如此一直上溯,直到不需對換或直到根節點為止。

push_heap算法的實現細節參見相關源碼。

注意

(1)為了滿足complete binary tree的條件,新加入的元素一定要放在最下一層作為葉節點,便填補在由左至右的第一個空格,也就是把新元素插入在底層vector的end()處。

(2)當push_heap函數被調用時,新元素應已置於底部容器的最尾端。

另: array無法動態改變大小,因此如果heap底層采用array,便不可以對滿載的array進行push_heap操作,因為那得先在array尾端增加一個元素。如果對一個滿載的array執行push_heap,該函數會將最后一個元素視為新增元素,並將其余元素視為一個完整的heap結構(實際上它們的確是),因此執行后的結果等於原先的heap。

2.2 pop_heap算法:下溯+上溯

下圖是 pop_heap算法的實際操演情況。既然身為max-heap,最大值必然在根節點。pop操作取走根節點(其實是設至底部容器vector的尾端節點)后,為了滿足complete binary tree的條件,必須割舍最下層最右邊的葉節點,並將其值重新安插至max-heap(因此有必要重新調整heap結構)。

為了滿足max-heap次序特性(每個節點的鍵值都大於或等於其子節點鍵值),我們執行所謂的percolate down(下溯)程序:將空間節點和其較大子節點“對調”,並持續下放,直至葉節點為止。然后將前述被割舍之元素值設給這個“已到達葉層的空間節點”,再對它執行一次percolate up(上溯)程序。

pop_heap算法的實現細節參見相關源碼。

注意

pop_heap之后,最大元素只是被置於底部容器的最尾端,尚未被取走。

2.3 sort_heap算法

既然每次pop_heap可獲得heap中鍵值最大的元素,如果持續對整個heap做pop_heap操作,每次將操作范圍從后向前縮減一個元素(因為pop_heap會把鍵值最大的元素放在底部容器的最尾端),當整個程序執行完畢時,我們便有了一個遞增序列。

2.4 make_heap算法

這個算法用來將一段現有的數據轉化為一個heap。

make_heap算法實現參見相關源碼

3. heap沒有迭代器 heap的所有元素都必須遵循特別的(complete binary tree)排列規則,所以heap不提供遍歷功能,也不提供迭代器。

 


免責聲明!

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



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