优先队列——斐波那契堆(without source code)
【0】README
0.1) 本文部分內(nèi)容轉(zhuǎn)自 數(shù)據(jù)結(jié)構(gòu)與算法分析,旨在理解 斐波那契堆 的基礎(chǔ)知識(shí);
0.2) 文本旨在理清 斐波那契堆的 核心idea,還沒有寫出源代碼實(shí)現(xiàn),表遺憾;
0.3)從實(shí)際角度看: 除了某些需要管理大量數(shù)據(jù)的應(yīng)用外,對(duì)于大多數(shù)應(yīng)用,斐波那契堆的常數(shù)因子和編程復(fù)雜性使得它比起普通二項(xiàng)堆(二項(xiàng)隊(duì)列)并不是那樣適用;
0.4)斐波那契堆(Fibonacci heap)通過添加兩個(gè)新的觀念推廣了二項(xiàng)隊(duì)列(Notions): (干貨——斐波那契堆通過添加兩個(gè)新的觀念推廣了二項(xiàng)隊(duì)列(Notions))
- N1)DecreaseKey 的一種不同的實(shí)現(xiàn)方法:以前的方法是把元素朝向根節(jié)點(diǎn)上濾。對(duì)于這種方法似乎沒有理由期望O(1)的攤還時(shí)間界,故需要新的方法;
- N2)懶惰合并(lazy merging): 只有當(dāng)兩個(gè)堆需要合并的時(shí)候才 合并。 對(duì)于懶惰合并,merge是低廉的, 但是因?yàn)閼卸韬喜⒉⒉粚?shí)際把樹結(jié)合到一起,所以deleteMin 操作可能會(huì)遇到許多的樹, 從而使這種操作的代碼高昂; 任何一次 deleteMin 都可能花費(fèi)線性時(shí)間, 但是總能夠把時(shí)間歸咎到前面的一些 merge操作中去。特別地, 一次昂貴的deleteMin 必須在其前面要有大量的非常低廉的 merge操作, 它們能夠存儲(chǔ)額外的位勢;(干貨——懶惰合并定義)
【1】切除左式堆中的節(jié)點(diǎn)
1.1)problem+solution:
- 1.1.1)problem:如果代表優(yōu)先隊(duì)列的樹不具有 O(logN)的深度,那么這種方法不適用。例如,若將這種方法用于左式堆,則 decreaseKey 操作可能花費(fèi) Θ(N)時(shí)間, 如下圖所示:
- 1.1.2)solution: 我們不想把0 上濾到根,因?yàn)檎缥覀兛吹降哪菢?#xff0c;存在一些情況使得這樣做代價(jià)太大。解決方法是把堆沿虛線 切開,如此得到兩棵樹,然后再把這兩棵樹合并為一顆樹。
1.2)具體操作
- 1.2.1)令 X 為 要執(zhí)行decreaseKey 操作的節(jié)點(diǎn), 令 P 為它的父節(jié)點(diǎn);在切斷以后,我們得到兩棵樹,即根為X的 H1 和 T2, T2 是原來的樹除去 H1 后 得到的樹;
1.3)problem+solution:
- 1.3.1)problem:如果這兩棵樹都是左式堆,那么它們可以以時(shí)間O(logN)合并完成了。但問題是, T2 未必是左式堆;
1.3.2)solution: 容易恢復(fù)左式堆的性質(zhì),這要用到下列兩個(gè)觀察到的結(jié)論(Conclusions):
- C1)只有從P 到 T2 的根的路徑上的節(jié)點(diǎn)可能破壞左式堆的性質(zhì), 它們可以通過變換子節(jié)點(diǎn)來調(diào)整;
- C2)由于最大右路徑最多有 」log(N)+1」個(gè)節(jié)點(diǎn),因此我們只需要檢查從P 到 T2 的根的路徑上的 前 」log(N)+1」 個(gè)節(jié)點(diǎn); 將T2 轉(zhuǎn)換為 左式堆H2 后的結(jié)構(gòu)如圖11-13所示:
1.4)因?yàn)?我們能夠以 O(logN) 步將T2 轉(zhuǎn)變成左式堆H2, 然后合并H1 和 H2, 所以得到一個(gè)在左式堆中執(zhí)行 decreaseKey 的O(logN) 算法;合并后的最后結(jié)果如圖11-14所示:
【2】二項(xiàng)隊(duì)列的懶惰合并
2.1)由斐波那契堆所使用的第二個(gè)想法是懶惰合并: 我們將把這個(gè)想法用于二項(xiàng)隊(duì)列并證明執(zhí)行一次merge操作(還有插入操作,它是一種特殊操作)的攤還時(shí)間為 O(1)。對(duì)于 deleteMin操作,其攤還時(shí)間仍然是 O(logN); (干貨——由斐波那契堆所使用的第二個(gè)想法是懶惰合并)
2.2)懶惰合并定義: 為了合并兩個(gè)二項(xiàng)隊(duì)列, 只要把兩個(gè)二項(xiàng)樹的表連在一起,結(jié)果得到一個(gè)新的二項(xiàng)隊(duì)列。這個(gè)新的二項(xiàng)隊(duì)列可能含有相同大小的多棵樹,因此破壞二項(xiàng)隊(duì)列的性質(zhì)。為了保持一致性,我們將把它叫做 懶惰二項(xiàng)隊(duì)列(lazy nominal queue)。這是一種快速操作, 該操作總是花費(fèi)常數(shù)時(shí)間。和前面一樣, 一次插入通過創(chuàng)建一個(gè)單節(jié)點(diǎn)二項(xiàng)隊(duì)列并將其合并而完成。 區(qū)別在于合并是懶惰的 。 (干貨——懶惰合并定義 + 懶惰二項(xiàng)隊(duì)列定義)
Attention)懶惰二項(xiàng)隊(duì)列和標(biāo)準(zhǔn)二項(xiàng)隊(duì)列的區(qū)別(Differentiation):
- D1)懶惰二項(xiàng)隊(duì)列: 允許有高度相同的二項(xiàng)樹存在該隊(duì)列中;
- D2)標(biāo)準(zhǔn)二項(xiàng)隊(duì)列: 不允許有高度相同的二項(xiàng)樹存在該隊(duì)列中;
2.3)deleteMin 操作要麻煩得多, 因?yàn)榇颂幮枰覀冏罱K把懶惰二項(xiàng)隊(duì)列轉(zhuǎn)換為 標(biāo)準(zhǔn)的二項(xiàng)隊(duì)列, 不過,它仍然花費(fèi)O(logN)的攤還時(shí)間——而不像以前是O(logN)最壞情形時(shí)間。為了執(zhí)行 deleteMin , 我們找出(并最終返回)最小元素。如前所述,我們將它從隊(duì)列中刪除, 使得它的每一個(gè)子節(jié)點(diǎn)都成為一顆新的 樹。此時(shí)我們通過合并兩顆相等大小的樹直至不再可能合并為止而把所有的樹合并成一個(gè)二項(xiàng)隊(duì)列;
- Attention)什么時(shí)候需要將懶惰二項(xiàng)隊(duì)列進(jìn)行合并? (干貨——什么時(shí)候需要將懶惰二項(xiàng)隊(duì)列進(jìn)行合并?)
- A1)懶惰二項(xiàng)隊(duì)列的定義: 是在需要的時(shí)候才進(jìn)行合并;不需要的時(shí)候不進(jìn)行合并,即只是將二項(xiàng)樹的表(根)連在一起而已;
- A2)懶惰二項(xiàng)隊(duì)列需要合并的時(shí)候是: 當(dāng)在執(zhí)行 deleteMin 操作之后 ,需要將 懶惰二項(xiàng)隊(duì)列進(jìn)行真正的合并;(after deleteMin operation)
2.4)看個(gè)荔枝:
2.4.1)懶惰二項(xiàng)隊(duì)列如圖11-15 所示:
2.4.2)為了執(zhí)行deleteMin, 我們刪除最小元素,得到的懶惰二項(xiàng)隊(duì)列如圖11-16所示:
2.4.3)現(xiàn)在我們必須將所有的樹合并而得到一個(gè)標(biāo)準(zhǔn)的二項(xiàng)隊(duì)列:使用的算法偽代碼如下:
對(duì)上述算法的分析(Analysis): 上述的 for 循環(huán)計(jì)數(shù) 和 while 循環(huán)末尾的檢測花費(fèi) O(logN) 時(shí)間, 這使得運(yùn)行時(shí)間成為所要要求的 O(T+logN)。
2.4.4)圖11-18 顯示該算法對(duì)前面二項(xiàng)樹的集合的執(zhí)行情況:
2.5)懶惰二項(xiàng)隊(duì)列的攤還分析
- 定理11.3: merge 和 insert 的攤還運(yùn)行時(shí)間對(duì)于懶惰二項(xiàng)隊(duì)列均為 O(1), 而deleteMin的攤還運(yùn)行時(shí)間為 O(logN);
【3】斐波那契堆操作(注意區(qū)分deleteMin 和 decreaseKey 操作)
3.1)正如我們前面提到的那樣: 斐波那契堆將左式堆 decreaseKey 操作與懶惰二項(xiàng)隊(duì)列merge操作結(jié)合起來。不過,我們不能一點(diǎn)都不修改。 (干貨——斐波那契堆的核心思想)
- 3.1.1)問題在于: 如果在這些二項(xiàng)樹中進(jìn)行任意切割,那么結(jié)果得到的森林將不再是二項(xiàng)樹的集合了。因此,每一顆樹的秩最多為 」logN」 將不再成立;
- 3.1.2)由于 在懶惰二項(xiàng)隊(duì)列中 deleteMin 的攤還時(shí)間已被證明是 2logN + R,因此,對(duì)于deleteMin 的界,我們需要 R = O(logN)成立;
3.2) 為了保證R=O(logN), 我們對(duì)所有的非根節(jié)點(diǎn)應(yīng)用下述法則(rules):
- r1)將第一次(因?yàn)榍谐?#xff09;失去一個(gè)子節(jié)點(diǎn)的(非根)節(jié)點(diǎn)做上標(biāo)記;
- r2)如果被標(biāo)記的節(jié)點(diǎn)又失去另外一個(gè)兒子節(jié)點(diǎn), 那么將其從它的父節(jié)點(diǎn)切除。這個(gè)節(jié)點(diǎn)現(xiàn)在變成了一顆分離的樹的根并且不再被標(biāo)記。這叫做一次 級(jí)聯(lián)切除,因?yàn)樵谝淮?decreaseKey 操作中可能出現(xiàn)多次這種切除; (干貨——級(jí)聯(lián)切除定義)
3.2)看個(gè)荔枝(注意和上述變換法則相結(jié)合):
3.2.1)圖11-19 顯示了在 decreaseKey 之前斐波那契堆中的一顆樹(被標(biāo)記的節(jié)點(diǎn)有: 8,11, 10, 17, 33)。 (干貨——現(xiàn)在講的操作是 decreaseKey)
- step1)當(dāng)關(guān)鍵字為 39 的節(jié)點(diǎn)變?yōu)?2的 時(shí)候,堆序被破壞;
- step2)由于包含33的節(jié)點(diǎn)已經(jīng)被標(biāo)記了(在對(duì)39進(jìn)行decrease之前),那么這是它的第二個(gè)失去的子節(jié)點(diǎn),從而它也被從它的父節(jié)點(diǎn)(10)中切除;(2nd rule applied)
- step3) 10也失去了它的第二個(gè)兒子, 于是10 從 根為5的樹中被切除;(2nd rule applied)
- step4)切除過程結(jié)束,因?yàn)?5 是 未做標(biāo)記的。現(xiàn)在把 節(jié)點(diǎn)5 做上標(biāo)記, 如圖11-20所示:(1st rule applied)
Attention)過去被做過標(biāo)記的節(jié)點(diǎn)10 和33 不再被標(biāo)記, 因?yàn)楝F(xiàn)在他們都是根節(jié)點(diǎn), 這在時(shí)間界的證明中是極其重要的;(Attention——過去被做過標(biāo)記的節(jié)點(diǎn)10 和33 不再被標(biāo)記, 因?yàn)楝F(xiàn)在他們都是根節(jié)點(diǎn)。)
補(bǔ)充:intro to 斐波那契堆(轉(zhuǎn)自http://dsqiu.iteye.com/blog/1714961)
- 斐波那契堆(Fibonacci Heap):斐波那契堆是一種松散的二項(xiàng)堆,與二項(xiàng)堆的主要區(qū)別在于構(gòu)成斐波那契堆得樹可以不是二項(xiàng)樹,并且這些樹的根排列是無須的(二項(xiàng)堆的根結(jié)點(diǎn)排序是按照結(jié)點(diǎn)個(gè)數(shù)排序的,不是按照根結(jié)點(diǎn)的大小)。斐波那契堆得優(yōu)勢在于它對(duì)建堆、插入、抽取最小關(guān)鍵字、聯(lián)合等操作能在O(1)的時(shí)間內(nèi)完成(不涉及刪除元素的操作僅需要O(1))。這是對(duì)二項(xiàng)堆效率的巨大改善。但由于斐波那契堆得常數(shù)因子以及程序設(shè)計(jì)上的復(fù)雜度,使它不如通常的二叉堆合適。因此,它的價(jià)值僅存在于理論意義上。
- 斐波那契堆的另一個(gè)特點(diǎn)就是: 合并操作只發(fā)生在抽取一個(gè)結(jié)點(diǎn)之后,也就是說斐波那契堆的維護(hù)總是會(huì)延后的(個(gè)人根據(jù)代碼理解的)。 (干貨——斐波那契堆與二項(xiàng)堆(二項(xiàng)隊(duì)列)的區(qū)別)
【4】時(shí)間界的證明
- 定理11.4:斐波那契堆對(duì)于 insert,merge 和 decreaseKey 的攤還時(shí)間界均為 O(1), 而對(duì)于deleteMin 則是 O(logN);
總結(jié)
以上是生活随笔為你收集整理的优先队列——斐波那契堆(without source code)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: XML——XSLT的一个简单荔枝
- 下一篇: 网络——连接到server