左偏樹&二項堆&斐波那契堆

左偏樹

定義

左偏樹(leftist tree 或 leftist heap),又被成爲左傾堆、左偏堆,最左堆等。

左傾樹是一棵二叉樹,它的節點除了和二叉樹的節點一樣具有左右子樹指針外,還有兩個屬性:鍵值零距離
(01) 鍵值的作用是來比較節點的大小,從而對節點進行排序。
(02) 零距離(英文名NPL,即Null Path Length)則是從一個節點到一個"最近的不滿節點"的路徑長度。不滿節點是指該該節點的左右孩子至少有有一個爲NULL。葉節點的NPL爲0,NULL節點的NPL爲-1。

[性質1] 節點的鍵值小於或等於它的左右子節點的鍵值。
[性質2] 節點的左孩子的NPL >= 右孩子的NPL。
[性質3] 節點的NPL = 它的右孩子的NPL + 1。
定理:若左偏樹有 NNN 個節點,則根節點的零距離(即最右路徑長度)不超過 log2(N1)+1\lfloor\log_2(N-1)\rfloor+1

合併

對於合併函數 merge(A,B)\mathrm{merge}(A, B)merge(A,B) ,合併兩個左偏堆 AAABBB

第一步有兩種情況:

  1. AAABBB 中任意一個爲空,就返回另一個樹。

  2. 合併根節點較小樹(這邊是小根堆)的右子樹和 BBB

如下圖,我們需要合併這兩個左偏堆,分別是 A,BA,BA,B ,其中 L1L1L1 , R1R1R1 , L2L2L2 , R2R2R2 爲子樹

a→data>b→dataa\rightarrow data>b\rightarrow dataadata>bdata ,爲了滿足左偏堆的性質, aaa 節點一定是 bbb 節點的父親,所以如下合併 AAA 的右子樹 R1R1R1BBB

反之亦然

第二步:

更新NPL,若不滿足左偏性質,交換左右子樹。

代碼如下

leftist_heap merge(leftist_heap &heap1, leftist_heap &heap2)
{
    if(heap1==NULL||heap2==NULL)//情況1
        return heap1==NULL? heap2:heap1;

    if(heap1->data>heap2->data)//爲了更好操作,我們這裏直接交換
        swap(heap1, heap2);

    heap1->right_child=merge(heap1->right_child, heap2);//遞歸合併右子樹
    if(!heap1->left_child||heap1->right_child->NPL>heap1->left_child->NPL)
        swap(heap1->left_child, heap1->right_child); //交換 

    heap1->pushup();//更新dist

    return heap1;
}

可以發現我們所有合併操作都是在最右路徑上操作,又因爲上面的定理,所以左偏堆合併的效率爲 O(log2n)\mathrm{O}(\log_2n)

其他操作

插入,即看成和一個單個節點的樹合併。

刪除最小值,即刪除根節點,然後左右子樹合併。


斐波那契堆

二項樹

二項樹有兩個定義:

第一種是:

  1. 度爲 000 的二項樹只包含一個節點。
  2. 度爲 k(k>0)k(k\gt0)k(k>0) 的二項樹有 kkk 個兒子,分別是度爲 k−1,k−2,…,1,0k-1,k-2,\dots,1,0k1,k2,,1,0 的二項樹

也可以使用第二種:

  1. 度爲 000 的二項樹只包含一個節點。
  2. 度爲 kkk 的二項樹由兩個度爲 k−1k-1k1 的樹組成,其中一個是另一個的根的最左兒子。

如圖:

二項樹的性質:

1.該樹含有2k2^k個節點。
2.樹的高度是 kkk
3.在深度 ddd 中含有 C(k,d)C(k,d)C(k,d) 個節點。
4.樹的根節點的度爲 kkk ,其它節點的度都小於 kkk

二項堆

二項堆是二項樹的集合,其中能滿足以下條件:

  1. 每一個節點都都有一個關鍵字,滿足孩子關鍵字大於(或小於)根節點的關鍵字。

  2. 每個二項樹度數互不相同

如圖:這是一個小根二項堆

二項堆合併:

如圖,合併兩個二項堆:

第一步:合併成一個列表並升序排序。

合併相同的兩個樹

以此類推

兩棵二項樹怎麼合併?

由二項樹定義可得,兩個相同的二項樹可以合併成一個更大的二項樹,其中一個根節點是新樹的根節點,一個是原樹的根節點,一個是最左兒子,參考堆的定義

插入不說

刪除即將需要刪除的節點移到根節點位置,然後刪除,合併所有孩子和原來的堆

其他可以參考刪除

斐波那契堆

斐波那契堆(Fibonacci heap)是堆中一種,它和二項堆一樣,也是一種可合併堆;可用於實現合併優先隊列。斐波那契堆比二項堆具有更好的平攤分析性能,它的合併操作的時間複雜度是O(1)。 與二項堆一樣,它也是由一組堆最小有序樹組成,並且是一種可合併堆。 與二項堆不同的是,斐波那契堆中的樹不一定是二項樹;而且二項堆中的樹是有序排列的,但是斐波那契堆中的樹都是有根而無序的。

斐波那契堆的每個節點需記錄其關鍵字、度數(子節點數)、左兄弟、右兄弟、第一個孩子節點、父節點,還有marked標記(marked在刪除節點時有用)。
而對於一個斐波那契堆,則需記錄堆中節點的總數、最大度、最小節點(某個最小堆的根節點)。
由於堆中的樹沒有規定的形狀,在極端情況下,堆中每個元素都是一棵單獨的樹。這種靈活性使得一些操作可以以“偷懶”的方式來執行,而“剩下”的工作將推遲到後面的操作中來完成。比如堆的合併僅僅將由樹所組成的鏈表鏈接起來,而降低元素值(decrease key)有時直接從父結點中剪斷而形成一棵新樹。

抽取最小結點的操作是斐波那契堆中較複雜的操作。該操作有以下兩個步驟:
(1)將要抽取最小結點的子樹都直接串聯在根表中;
(2)合併所有degree相等的樹,直到沒有相等的degree的樹。

而改變節點值則是斐波那契堆中最複雜的操作。以減小元素值爲例,假設一個結點P的元素值爲A,現在要將其降低爲B,其父結點的值爲C,則遵循這樣的規則:
1、若B>=C, 則直接將A替換爲B即可
2、若B<C, 結點P的父結點是根結點,則直接將結點P從根結點處剪斷,形成一棵新樹
3、若B<C, 結點P的父結點不是根結點,則將結點P從父結點處剪斷,形成一棵新樹,然後將父結點設置爲marked
4、若B<C, 並且父結點已經被marked了,則不僅要將結點P從父結點處剪斷,而且依次往上遞歸將被marked的父結點也從其父結點處剪斷,直到碰到一個父結點沒有被marked,或者碰到根結點爲止

這樣,我們運用斐波那契堆,求最小值(find-mininum), 插入(insert), 降低元素值(decrease-key)和合並(merge/union)可以便在O(1)\mathrm{O}(1)時間內完成。刪除(delete)和刪除最小值(delete minimun)可以在O(log2n)\mathrm{O}(\log_2n)均攤時間內完成。


附表:


聲明:本文大部分參考自左偏堆&斐波那契堆斐波那契堆(一)之 圖文解析 和 C語言的實現斐波那契堆

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章