資料結構重點複習摘要
Fibonacci heap 支援 mergeable heap (e.g. Binomial Heap)的各個操作 (Make-Heap()
, Insert(H, x)
, Minimum(H)
, Extract-Min(H)
, Union(H1, H2)
)
除此之外 Fibonacci heap 最大的特點在於它有些 operations run in constant amortized time
註: Make-Heap()
指建構空 heap
Procedure | Binary heap (worst case) |
Fibonacci heap (amortized) |
---|---|---|
Make-Heap |
||
Insert |
||
Minimum |
||
Extract-Min |
||
Union |
||
Decrease-Key |
||
Delete |
註: DECREASE-KEY(H,x,k)
指將 heap H
中的元素 x
之值換成不比原本大的值 k
,也就是把 x->val
變小成 k
我們很清楚,一般的 binary heap 在 merge (Union
) 時的表現可能會不太理想
Fibonacci heap 明顯的優勢就是均攤之下有些操作的表現比較好
可以看到有不少都能達到常數時間
以理論來說,我們這邊談論的場景主要是 Extract-Min
跟 Delete
兩個操作相對其他操作來得少次的情況,例如在圖論問題中有些演算法會需要對每個邊做 Decrease-Key
對於比較密集 (多邊) 的圖, Fibonacci heap 提供的常數時間操作會帶來很大的表現提升
然而回到現實面, Fibonacci heap 寫起來太麻煩了,沒事不會有人想用 (不是說完全沒人用)
考試這邊大概就考觀念,不過本文還是會大致講一下實作部分
Fibonacci heap 你可以想成是一個 Forest 的資料結構
它是一堆 heap-ordered trees ,也就是每棵樹都滿足父點不大於子點 (the key of a node is greater than or equal to the key of its parent)
Each node
x
contains a pointerx.p
to its parent and a pointerx.child
to any one of its children. The children ofx
are linked together in a circular, doubly linked list, which we call the child list ofx
. Each childy
in a child list has pointersy.left
andy.right
that point toy
’s left and right siblings, respectively. If nodey
is an only child, theny.left = y.right = y
. Siblings may appear in a child list in any order.
有了上述性質,我們只要找到 root
最小的樹,其 root
就是整個 Fibonacci heap 的最小值
用一個指標指向該節點,如此一來 Minimum
操作就能是
兄弟之間會互指 (形成環狀雙向鏈結),子點都會指向父點,父點會指向自己的其中一個子點
如果是唯一的子點,就會左右指向自己
環狀雙向鏈結串列在這邊有兩個好處:
除此之外,節點的資料結構還會再包含兩個屬性:
degree
: 節點的子串列節點數mark
: boolean 值,用來表示該節點是否失去了自己的子節點Fibonacci heap 要刪或減時都挺麻煩的,上面這邊將在到時候派上用場
另外 Fibonacci heap 本身我們會再紀錄一下有幾個節點
For a give Fibonacci heap , ew indicate by the number of trees in the root list of and by the number of marked nodes in .
We defined the potential of by
Let be an upper bound on the maximum degree of any node in an nodes Fibonacci heap,
merge 2 Fibonacci heap 有兩類操作
這邊的 Union
精神即 lazy merge
基本就只是把 root 接起來而已,然後看一下現在 min 應該指向誰的
可想而知這邊會是
minNode
有兩種情況需要變成對方的 minNode
:
minNode
且它才是最小那之後就把節點數加起來
如果我們把要插入的節點看做一個 Fibonacci heap
Insert
不就也是一種 Union
嗎?
給 minNode
就好,這邊很簡單
前面都很簡單,這邊開始就真的複雜起來了
我們前面 merge 都用 lazy merge 應付著,但這邊開始我們就需要做 consolidate
了
要刪掉最小值的想法還是在 merge ,我們將 minNode
的每個子節點都當成 root ,並從 root list 中移除 minNode
這個過程你可想成原本的 Fibonacci heap 已經被搞散架了,此時我們需要將有相同度數的 tree 合併,直到不能再合併為止,這個視為一種修復重組,也就是 consolidate
上面這段 code 中
做的就是把 minNode
的子點都拆開來,使用 lazyMerge
把它們都先加到 root list
之後,讓 minNode
的左右兄弟把手收回來
如果 minNode
的右邊指向自己,表示它是 root list 中唯一的那位且它沒有子點
除此之外我們就執行 consolidate
前面講過
所以 maxDegree = static_cast<int>(std::log2(nodeCnt)) + 1
那之後,假設我們在 root list 中找到兩個有相同 degree
的節點 x
跟 y
,不失一般性假設
那麼為了符合 minimum-heap property ,我們肯定是將 y
接給 x
(使用 link
實現)
換句話說: y
會從 root list 中拔掉,而 x
的 degree
會增加
最後那段是要重新選出 minNode
這邊不難看出時間複雜度會是 (即使不實際算 amortized cost 大概也能看出)
我們把某個節點的值變小後,如果它比父點還小,那就破壞我們資料結構的性質了
首先我們要先讓該節點與父點訣別 (使用 cut
)
接著就是一直不斷往祖宗那挑戰 (cascadingCut
)
可以看到在這邊我們才真正用到了 mark
,我們透過這個標記來記錄節點的歷史
當某個節點經歷了以下事件:
cut
掉若x
經歷了1. 跟 2. 且已經失去一個子點,此時他的 mark
會被設成 true
,表示它離被 cut
掉只差一點點了
實際上就是第一次的 cascadingCut
會執行 if (!node->mark)
這段,但第二次再來就會執行到 else
了
這個動作可以預想,如果 x
是 y
失去的第二個子點,那 y
也要面對上述同樣的事情,對吧?所以這邊還要 cascadingCut(p)
另外容易忽略的是,在 cut
應該有注意到我們做了 lazyMerge(minNode, node)
此時 node
又再次經歷 1. ,所以我們要把 mark
重置回 false
假設原本有 顆 trees ,有 顆 trees 經歷 cascadingCut
後被拆出來
假設總共有 c-1
顆的 mark
被 cascadingCut
unmark ,但最後一次的 cascadingCut
可能會再 mark 一個 node ,故最多有 個 marked nodes
則 potential 的變化最多會是:
你會發現, decreaseKey
的 amortized cost 最多也就
Fibonacci 的一大考點就是它 decreaseKey
均攤之下僅需常數時間
刪除節點就簡單了,我們只要將要刪除的節點值變成最小,extractMin
所刪掉的肯定是該點
最後附上較完整的 code
我沒有認真 debug 過,大概看得懂概念就好,反正考試應該頂多考畫圖🤔