可合併堆是支援以下5
種操作的資料結構,其中每個元素都有乙個關鍵字:
make-heap():建立和返回乙個新的不含任何元素的堆。
insert(h, x):將乙個已填入關鍵字的元素x插入堆h中。
minimum(h):返回乙個指向堆h中具有最小關鍵字元素的指標。
extract-min(h):從堆h中刪除最小關鍵字的元素,並返回指向該元素的指標。
union(h1,h2):建立並返回乙個包含堆h1和堆h2中所有元素的新堆。堆h1和h2被銷毀。
斐波那契堆支援可合併堆的操作,而且還支援下面的兩種操作:
decrease-key(h, x, k):將堆h中元素x的關鍵字賦予新值k。假定新值k不大於當前的關鍵字。
delete(h, x):從堆h中刪除元素x。
斐波那契堆的時間複雜度與普通的堆相比效能如下:
斐波那契堆對於操作insert, union和decrease-key,比起普通的堆有更好的時間效能。注意,上圖中,斐波那契堆的時間是攤還時間。
理論上看,如果extract-min和delete操作的數目相比於其他操作小得多的時候,斐波那契堆尤其適用,這種情形出現在很多應用中:一些問題(計算最小生成樹和尋找單源最短路徑)中必不可少的用到斐波那契堆。
但是實際上,除了需要管理大量資料的應用外,對於大多數應用,斐波那契堆的常數因子和程式設計複雜性使得它比普通二項堆並不那麼適用。
斐波那契堆對於search操作的支援比較低效,所以,設計給定元素的操作均需要乙個指標指向這個元素。
一:斐波那契堆結構
乙個斐波那契堆是一系列具有最小堆序的有根數的集合。也就是堆中的每棵樹都滿足最小堆性質:每個節點的關鍵字大於或等於父節點的關鍵字。如下圖就是乙個例子:
每個節點x包含乙個指向父節點的指標x.p。乙個指向它的某乙個孩子的指標x.child。x的所有孩子被連線成乙個環形的雙向鍊錶,稱為x的孩子鍊錶。孩子鍊錶中的每個孩子y均有指標y.left
和y.right,分別指向y的左兄弟和右兄弟。如果y是僅有的孩子,則y.left=y.right=y。孩子鍊錶中各兄弟出現的次序是任意的。
使用雙向鍊錶可以在
o(1)
時間內在任何位置插入或者刪除乙個節點,而且也可以在
o(1)
時間內將兩個這樣的鍊錶連線成乙個迴圈鍊錶。
每個節點還有兩個屬性:節點x的孩子鍊錶中的孩子數目為x.degree;x.mark表示x自從上一次成為某個節點的孩子後,是否失去過孩子。
通過指標h.min訪問給定的斐波那契堆h。該指標指向具有最小關鍵字的節點。如果不止乙個節點具有最小關鍵字,則任意乙個就可以成為最小節點。如果h.min = null,則該斐波那契堆是空的。
在斐波那契堆中,所有樹的根節點,通過其left和right指標也形成乙個環形的雙向鍊錶。稱為根鍊錶。根煉表中,樹的次序可以使任意的。
h.n表示當前堆h中節點的數目。
下圖是乙個包含屬性細節的斐波那契堆,後續稱斐波那契堆為f堆:
在f堆中,對於攤還分析均假定,在乙個n個節點的f堆中,任何節點的最大度數都有上界d(n)。其中d(n)<=o(lg n)
二:可合併堆操作
f堆上的一些可合併堆操作要盡可能的延後執行。不同的操作可以進行效能平衡。比如:通過將乙個新節點加入根鍊錶的方式來插入乙個節點。如果從空的
f堆開始,插入
k個節點,則
f堆將是乙個正好包含
k的節點的雙向迴圈鍊錶。
此時,如果在f堆h上執行乙個extract-min操作,在移除h.min後,需要遍歷根煉表中剩下的k-1個節點找出新的最小節點。這裡便存在效能平衡問題。只要遍歷整個根鍊錶,並且把節點合併到最小堆序樹中以減小根鍊錶的規模。執行完該操作後,根煉表中的每個節點與其他節點的度數均不同。
1:建立乙個新的f堆
make-fib-heap建立新的f堆,其中h.n=0,h.min=null。make-fib-heap的攤還代價等於實際代價o(1)。
2:插入乙個節點
fib-heap-insert
,就是將新節點插入到根煉表中。該操作的攤還代價與實際代價都是o(1):
3:尋找最小節點
f堆的最小節點可以通過h.min得到。因此,尋找最小節點的實際代價和攤還代價都是o(1)。
4:兩個f堆的合併
合併兩個f堆h1和h2,簡單的將h1和
h2的根鍊錶鏈結,然後找到新的最小節點即可,之後h1和h2將被銷毀。合併操作fib-heap-union的攤還代價和實際代價都是o(1):
5:抽取最小節點
抽取最小節點操作,包括合併樹的延後工作。**中假設乙個節點
z被從根煉表中移除之後,留在根煉表中的指標更新,但是
z的指標並未改變。
該演算法將最小節點z的每個孩子都變為根節點,並從根煉表中刪除最小節點。然後通過
consolidate
操作將根煉表中,具有相同度數的節點合併,直到根煉表中的每個節點都具有不同的度數。
這裡要注意的是,執行完第6行之後,雖然z被從根煉表中移除了,但是z的指標保持不變,如果此時z=z.right的話,則說明z是根煉表中僅有的乙個節點,而且沒有孩子節點。否則,z.right會指向根煉表中的其他節點,或者是原來的孩子節點(因為孩子節點已經上公升到根煉表中了)。
consolidate
操作減少
f堆中樹的數目,將根煉表中度數相同的節點進行合併。其中要用到輔助陣列
a[0..d(h.n)]
,如果a[i]=y
,則表示度數為
i的節點為
y。演算法如下:
在consolidate演算法中,首先初始化陣列a[0..d(n)]為null,然後依次遍歷根煉表中的每個節點x,如果碰到a[d] != null的情況,說明,之前具有與x相同度的節點y,根據x和y的關鍵字大小,將x與y進行合併,使y成為x的孩子。這樣x的度數就增加了1,然後繼續處理,直到a[d]為空。最終,使得a[d]=x。這樣處理後續根節點時,x
是已處理過的根節點中具有度數為
d的唯一節點。fib-heap-extract-min的攤還代價為o(d(n)) = o(lg n)。
三:關鍵字減值和刪除乙個節點
1:關鍵字減值
x的關鍵字變小以後,如果違反了最小堆的性質,首先進行「切斷」,直接將x切斷與其父節點y之間的聯絡,使x上公升到根煉表中,使其成為根節點。如果x的父節點y已經失去了兩個孩子(失去第乙個孩子後,y.mark=true),則需要向上級聯切割。這主要為了保證最大度數的界為o(lg n)。fib-heap-decrease-key的攤還代價為o(1)。
2:刪除乙個節點
刪除乙個節點的攤還代價為o(d(n)) = o(lg n)
演算法導論19(斐波那契堆)
如果不對斐波那契堆做任何decrease key或delete操作,則堆中每棵樹就和二項樹一樣 但是如果執行這兩種操作,在一些狀態下必須要破壞二項樹的特徵,比如decrease key或delete後,有的樹高為k,但是結點個數卻少於2 k。這種情況下,堆中的樹不是二項樹。為什麼要進行級聯剪下,級聯...
演算法導論之斐波那契堆
斐波那契堆,和二項堆類似,也是由一組最小堆有序的樹構成。注意區別,不是二項樹,是有根而無序的樹。導論中,斐波那契堆只是具有理論上的意義,是以平攤分析為指導思想來設計的資料結構,主要是漸進時間界比二項堆有改善。斐波那契堆除去刪除元素操作外,其他操作只有o 1 的平攤執行時間,而二項堆需要o lgn 的...
斐波那契堆
以下是實現的程式 肯定可以再優化的。include include include include using namespace std class node delete m child m child null class fibonacciheap node insert int key v...