上一篇介紹了斐波那契堆的節點結構、類定義與基本屬性。本文將深入講解插入、合併、刪除最小節點、鏈接、鍵值更新、刪除節點等核心操作的算法邏輯與 C++ 實現細節。
思路:新節點單獨構成一棵樹,直接插入根鏈表的 min 節點之前(雙向循環鏈表尾部)。若新節點鍵值更小,更新 min 指針。
template <class T>
void FibHeap<T>::addNode(FibNode<T>* node, FibNode<T>* root) {
node->left = root->left;
root->left->right = node;
node->right = root;
root->left = node;
}
template <class T>
void FibHeap<T>::insert(FibNode<T>* node) {
if (keyNum == 0) min = node;
else {
addNode(node, min);
if (node->key < min->key) min = node;
}
++keyNum;
}
關鍵點:根鏈表是雙向循環鏈表,min 作為「頭結點」,插入到 min 之前相當於插到表尾,O(1) 指針操作。
思路:將兩堆的根鏈表拼接成一個雙向循環鏈表,比較兩堆 min 更新全局最小,釋放被合併堆的輔助內存 cons。
template <class T>
void FibHeap<T>::catList(FibNode<T>* a, FibNode<T>* b) {
FibNode<T>* tmp = a->right;
a->right = b->right;
b->right->left = a;
b->right = tmp;
tmp->left = b;
}
template <class T>
void FibHeap<T>::combine(FibHeap<T>* other) {
if (!other) return;
if (other->maxDegree > maxDegree) swap(*this, *other); // 保證 this 容量較大
if (!min) { min = other->min; keyNum = other->keyNum; }
else if (other->min) {
catList(min, other->min);
if (other->min->key < min->key) min = other->min;
keyNum += other->keyNum;
}
free(other->cons);
delete other;
}
最複雜的操作,分三步:
cons[degree] 數組記錄每度數的樹,相同度數則 link 合併,直到每度數最多一棵樹template <class T>
FibNode<T>* FibHeap<T>::extractMin() {
FibNode<T>* z = min;
if (z) {
// 1. 子節點提升為根
if (z->child) {
FibNode<T>* child = z->child;
do {
FibNode<T>* next = child->right;
addNode(child, min); // 插入根鏈表
child->parent = nullptr;
child = next;
} while (child != z->child);
}
// 2. 從根鏈表移除 z
removeNode(z);
if (z == z->right) min = nullptr; // 只剩 z 一個節點
else {
min = z->right;
consolidate(); // 核心合併
}
--keyNum;
}
return z;
}
template <class T>
void FibHeap<T>::consolidate() {
makeCons(); // 確保 cons 數組足夠大
// 初始化 cons 為 nullptr
for (int i = 0; i <= maxDegree; ++i) cons[i] = nullptr;
// 遍歷根鏈表
int numRoots = 0;
FibNode<T>* x = min;
do { ++numRoots; x = x->right; } while (x != min);
while (numRoots--) {
FibNode<T>* curr = x;
x = x->right;
int d = curr->degree;
while (cons[d]) {
FibNode<T>* y = cons[d];
if (curr->key > y->key) swap(curr, y); // 保證 curr 為較小者
link(y, curr); // y 成為 curr 的子
cons[d] = nullptr;
++d;
}
cons[d] = curr;
}
// 重建根鏈表並找新 min
min = nullptr;
for (int i = 0; i <= maxDegree; ++i) {
if (cons[i]) {
if (!min) {
min = cons[i];
min->left = min->right = min;
} else {
addNode(cons[i], min);
if (cons[i]->key < min->key) min = cons[i];
}
}
}
}
僅允許減小鍵值(最小堆性質)。若減小後小於父節點,觸發 Cut(剪切) 並可能引發 Cascading Cut(級聯剪切)。
template <class T>
void FibHeap<T>::decrease(FibNode<T>* node, T key) {
if (key > node->key) return; // 不允許增大
node->key = key;
FibNode<T>* parent = node->parent;
if (parent && node->key < parent->key) {
cut(node, parent);
cascadingCut(parent);
}
if (node->key < min->key) min = node;
}
template <class T>
void FibHeap<T>::cut(FibNode<T>* node, FibNode<T>* parent) {
// 從父節點子鏈表移除
removeNode(node);
--parent->degree;
// 加入根鏈表
addNode(node, min);
node->parent = nullptr;
node->marked = false;
}
template <class T>
void FibHeap<T>::cascadingCut(FibNode<T>* node) {
FibNode<T>* parent = node->parent;
if (parent) {
if (!node->marked) node->marked = true; // 首次失去子,標記
else {
cut(node, parent); // 二次失去子,剪切上移
cascadingCut(parent); // 遞歸向上
}
}
}
marked 標記意義:記錄節點是否已失去過一個孩子。首次失去只標記,二次失去才剪切,這保證了度數下界為 Fibonacci 數列,從而證明 maxDegree = O(log n)。
remove(key) = decrease(node, -∞) + extractMin()。
模板類中用 numeric_limits<T>::min() 或自定義極小值實現。
斐波那契堆不支持高效查找,需在根鏈表及各子樹中遞歸 DFS 查找,僅用於演示/調試。
int main() {
FibHeap<int> heap;
vector<int> vals = {5, 2, 8, 1, 9, 3};
for (int v : vals) heap.insert(new FibNode<int>(v));
cout << "Min: " << heap.minimum() << endl; // 1
heap.removeMin();
cout << "Min after pop: " << heap.minimum() << endl; // 2
// 測試 decrease key
FibNode<int>* node = heap.search(9);
heap.decrease(node, 0);
cout << "Min after decrease 9->0: " << heap.minimum() << endl; // 0
heap.print(); // 結構驗證
return 0;
}
| 操作 | 二項堆 | 斐波那契堆 |
|---|---|---|
| Insert | O(log n) | O(1) 平攤 |
| Merge | O(log n) | O(1) 平攤 |
| ExtractMin | O(log n) | O(log n) 平攤 |
| DecreaseKey | O(log n) | O(1) 平攤 |
| Delete | O(log n) | O(log n) 平攤 |