也许我们每个人都知道斐波那契数列(Fibonacci sequence)。即这样一个数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144...,如果我们用伪代码比表示:

int FibonacciSequence(int n){if (n == 1 || n == 2) {return 1;}return FibonacciSequence(n - 1) + FibonacciSequence(n - 2);
}

接下来我们会知道为什么叫斐波那契堆,本文不涉及任何的分析(这里平摊分析(Amortized analysis))。

1. 概述

一个斐波那契堆是一系列具有最小堆序的有根树的集合。(也就是每棵树都遵循最小堆性质)[1]

2. 斐波那契堆的结构

[2]

从上面我们可能发现,我们堆斐波那契堆的描述,并不完全符合我们的定义(那时因为定义是针对在一次extractMin(删除最小值)操作后,使之符合我们的定义)。但从上面的结构图我们很容易看出其详细的结构,以及指针域。兄弟节点之间用双向链表进行链接,父节点指向其中一个孩子节点,而所有的子节点都会指向其父亲节点。有些节点还被标记了(后面我们会知道,只有当父节点失去孩子节点时,会被标记)。上述还有一个隐含的关键字那就是每个节点的度(即拥有孩子节点的个数)

3. 基本数据结构

typedef int ElemType;// the structure of each node of this heap
typedef struct FiboNode{// the key_valueElemType data;struct FiboNode *p;struct FiboNode *child;struct FiboNode *left;struct FiboNode *right;// the number of its childrenint degree;// indicates whether the node lost a childint marked;
} FiboNode;
// generate a node
FiboNode * generateFiboNode(ElemType data) {FiboNode * fn = NULL;fn = (FiboNode *) malloc(sizeof(FiboNode));if (fn != NULL) {fn->p = NULL;fn->child = NULL;fn->left = NULL;fn->right = NULL;fn->data = data;fn->marked = FALSE;} else {printf("memory allocation of FiboNode failed");}return fn;
}
// represent the fibonacci heap
typedef struct FibonacciHeap{// pointer pointing to min key in the heapFiboNode *min;// the number of nodes the heap containingint n;
} *FibonacciHeap;
// create Fibonacci Heap, and return it
FibonacciHeap makeFiboHeap() {FibonacciHeap h = NULL;h = (struct FibonacciHeap *) malloc(sizeof(struct FibonacciHeap));if (h == NULL) {printf("memory allocation of FibonacciHeap failed");}h->min = NULL;h->n = 0;return h;
}

4. 插入

插入其实很简单,就是把待插入的元素插入根列表(root list)中去,即上图[2]中min[H]指向的那一行。

// union two heap
FibonacciHeap heapUnion(FibonacciHeap *h1, FibonacciHeap *h2) {// using a new heapFibonacciHeap h = makeFiboHeap();if (h != NULL) {// concatenate the root list of h with h1 and h2if ((*h1)->min == NULL) {h->min = (*h2)->min;} else if ((*h2)->min == NULL) {h->min = (*h1)->min;} else {FiboNode *min_h1 = (*h1)->min;FiboNode *min_right_h1 = min_h1->right;FiboNode *min_h2 = (*h2)->min;FiboNode *min_right_h2 = min_h2->right;min_h1->right = min_right_h2;min_right_h2->left = min_h1;min_h2->right = min_right_h1;min_right_h1->left = min_h2;if ((*h1)->min->data > (*h2)->min->data) {h->min = (*h2)->min;} else {h->min = (*h1)->min;}}// release the free memoryfree(*h1);*h1 = NULL;free(*h2);*h2 = NULL;// update the nh->n = (*h1)->n + (*h2)->n;}return h;
}

5. 合并(Union)

这个操作只需要将两个斐波那契堆的根列表合并,并使得min[H]指向两者间的最小值节点。

// union two heap
FibonacciHeap heapUnion(FibonacciHeap *h1, FibonacciHeap *h2) {// using a new heapFibonacciHeap h = makeFiboHeap();if (h != NULL) {// concatenate the root list of h with h1 and h2if ((*h1)->min == NULL) {h->min = (*h2)->min;} else if ((*h2)->min == NULL) {h->min = (*h1)->min;} else {FiboNode *min_h1 = (*h1)->min;FiboNode *min_right_h1 = min_h1->right;FiboNode *min_h2 = (*h2)->min;FiboNode *min_right_h2 = min_h2->right;min_h1->right = min_right_h2;min_right_h2->left = min_h1;min_h2->right = min_right_h1;min_right_h1->left = min_h2;if ((*h1)->min->data > (*h2)->min->data) {h->min = (*h2)->min;} else {h->min = (*h1)->min;}}// release the free memoryfree(*h1);*h1 = NULL;free(*h2);*h2 = NULL;// update the nh->n = (*h1)->n + (*h2)->n;}return h;
}

6. 抽取最小节点

从上面我们很容易知道如何操作的。
1)具体步骤如下:
step1. 让最小节点的的所有子节点添加到根列表中去。
step2. 从根列表中删除最小节点。
step3. 创建一个log(n) + 1 的节点指针数组A。
step4. 对根列表进行合并,使得其满足我们堆斐波那契堆的定义(即根列表中的节点的度数具有唯一性)

// extract minimum node in this heap
FiboNode * extractMinimum(FibonacciHeap h) {// the minimum nodeFiboNode * z = h->min;if (z != NULL) {FiboNode * firstChid = z->child;// add the children of minimum node to the root list.if (firstChid != NULL) {FiboNode * sibling = firstChid->right;// min_right point the right node of minimum nodeFiboNode * min_right = z->right;// add the first child to the root listz->right = firstChid;firstChid->left = z;min_right->left = firstChid;firstChid->right = min_right;firstChid->p = NULL;min_right = firstChid;while (firstChid != sibling) {// record the right sibling of siblingFiboNode *sibling_right = sibling->right;z->right = sibling;sibling->left = z;sibling->right = min_right;min_right->left = sibling;min_right = sibling;sibling = sibling_right;// update the psibling->p = NULL;}}// remove z from the root listz->left->right = z->right;z->right->left = z->left;// the root list has only one nodeif (z == z->right) {h->min = NULL;// the children of z shoud be the root list of the heap// and find the minimum in this heapif (z->child != NULL) {FiboNode *child = z->child;h->min = child;FiboNode *sibling = child->right;while (child != sibling) {if (h->min->data > sibling->data) {h->min = sibling;}sibling = sibling->right;}}} else {h->min = z->right;consolidate(h);}h->n -= 1;}return z;}

2)对根列表进行合并
1. link操作
把节点y链接到x:并把y从根列表中删除,此时x会成为y的父亲,y会成为x的孩子,同时x节点的度数(degree)会增加1.

// make y a child of x
void heapLink(FibonacciHeap h, FiboNode *y, FiboNode *x) {// remove y from the root list of hy->left->right = y->right;y->right->left = y->left;// make y a child of x, incrementing x.degreeFiboNode * child = x->child;if (child == NULL) {x->child = y;y->left = y;y->right = y;} else {y->right = child->right;child->right->left = y;y->left = child;child->right = y;}y->p = x;x->degree += 1;y->marked = FALSE;
}

2. 合并
创建一个辅助数组A[0, 1..., D(H.n)]来记录根节点对应的度数的轨迹。如果A[i] = y, 则degree[y] = i.通过遍历根列表,若对应度数的数组A[i]为NILL,表示还没有记录,则使得A[i] = y, 否则存在两个度数相同的度数的根列表节点,则我们需要对其进行link操作,使之度数增加1,并设置A[i] = NILL, 然后再探查A[i + 1]是否为NIll, 为NILL则记录,否则再进行link操作,这样循环往复。

void consolidate(FibonacciHeap h) {int dn = (int)(log(h->n) / log(2)) + 1;FiboNode * A[dn];int i;for (i = 0; i < dn; ++i) {A[i] = NULL;}// the first node we will consolidateFiboNode *w  = h->min;// the final node in this heap we will consolidateFiboNode *f = w->left;FiboNode *x = NULL;FiboNode *y = NULL;int d;// tempFiboNode *t = NULL;while (w != f) {d = w->degree;x = w;w = w->right;while (A[d] != NULL) {// another node with the same degree as xy = A[d];if(x->data > y->data) {t = x;x = y;y = t;}heapLink(h, y, x);A[d] = NULL;d += 1;}A[d] = x;}// the last node to consolidate (f == w)d = w->degree;x = w;while (A[d] != NULL) {// another node with the same degree as xy = A[d];if(x->data > y->data) {t = x;x = y;y = t;}heapLink(h, y, x);A[d] = NULL;d += 1;}A[d] = x;int min_key = 100000;h->min = NULL;// to get min in this heapfor (i = 0; i < dn; ++i) {if(A[i] != NULL && A[i]->data < min_key) {h->min = A[i];min_key = A[i]->data;}}
}

7. 降低节点的值

直接上代码这样,接下来分析才比较容易。

// decrease the data of given node to the key
void heapDecreaseKey(FibonacciHeap h, FiboNode *x, ElemType key) {if (key >= x->data) {printf("new key is's smaller than original value");return;}x->data = key;FiboNode *y = x->p;// if x->data >= y->data, do nothing// else we need cutif (y != NULL && x->data < y->data) {cut(h, x, y);cascadingCut(h, y);}// get min node of this heapif (x->data < h->min->data) {h->min = x;}
}
// if we cut node x from y, it indicates root list of
// h not null
void cut(FibonacciHeap h, FiboNode *x, FiboNode *y) {// remove x from child list of y, decrementing degree[y]if(y->degree == 1) {y->child = NULL;} else {x->left->right = x->right;x->right->left = x->left;// update child[y]y->child = x->right;}// add x to the root list of hx->left = h->min;x->right = h->min->right;h->min->right = x;x->right->left = x;// updating p[x], marked[x] and decrementing degree[y]x->p = NULL;x->marked = FALSE;y->degree -= 1;
}
void cascadingCut(FibonacciHeap h, FiboNode *y) {FiboNode *z = y->p;if (z != NULL) {if (y->marked == FALSE) {y->marked = TRUE;} else {cut(h, y, z);cascadingCut(h, z);}}
}

通过上面的发现我们知道:
1)当我们降低节点x的值时,值一定要小于当前节点x的值
2)若节点x的值比其父亲要小,则已经满足不了最小堆性质
3)当不满足最小堆性质的时候,我们要将其与其父亲y切断,并将x添加到根列表中去
4)若x的父亲在失去第一个孩子的时候则要Mark = true,当y失去第2个孩子,则y要和y的父亲z切断,接着这样不断上升操作。

8. 删除

删除的操作比较简单,就两步:
1)将节点的值降低到无穷小
2)进行extractMinimum() 操作
void heapDelete(FibonacciHeap h, FiboNode *x) {
    heapDecreaseKey(h, x, -10000);
    extractMinimum(h);
}

参考:

[1] Thomas H.Cormen、Charles E.Leiserson《算法导论》第三版 第十九章 “斐波那契对” p291
[2]  Thomas H.Cormen、Charles E.Leiserson《算法导论》第三版 第十九章 “斐波那契对” p291
[2]  Thomas H.Cormen、Charles E.Leiserson《算法导论》第三版 第十九章 “斐波那契对” p295

感谢 Thomas H.Cormen、Charles E.Leiserson等人
若有参考遗漏的请抱歉,也请您留言
若其中有误的请多多留言不甚感激

完整源码,请点击

注:转载请注明

斐波那契堆(Fibonacci Heap)相关推荐

  1. 斐波那契堆(Fibonacci heaps)

    一:斐波那契堆 1:特性 斐波那契堆同二项堆一样,也是一种可合并堆.斐波那契堆的优势是:不涉及删除元素的操作仅需要O(1)的平摊运行时间(关于平摊分析的知识建议看<算法导论>第17章).和 ...

  2. 优先队列——斐波那契堆(without source code)

    [0]README 0.1) 本文部分内容转自 数据结构与算法分析,旨在理解 斐波那契堆 的基础知识: 0.2) 文本旨在理清 斐波那契堆的 核心idea,还没有写出源代码实现,表遗憾: 0.3)从实 ...

  3. 《算法导论》第19章-斐波那契堆 引入 19.1 斐波那契堆结构

    引入 1.可合并堆 可合并堆(mergeable heap)是支持以下5种操作: MAKE-HEAP():创建和返回一个新的不含任何元素的堆. INSERT(H,x):将一个已填人关键字的元素x插人堆 ...

  4. 《算法导论3rd第十九章》斐波那契堆

    前言 第六章堆排序使用了普通的二叉堆性质.其基本操作性能相当好,但union性能相当差. 对于一些图算法问题,EXTRACT-MIN 和DELETE操作次数远远小于DECREASE-KEY.因此有了斐 ...

  5. 斐波那契堆为何称为斐波那契

    本文只讨论斐波那契堆与斐波那契数列的关系,不对其增删改查做说明.(只是讨论) 废话一句: 斐波那契数列的递推公式:an=an-1+an-2 废话两句: 剪枝操作: 一个节点不能丢掉两个子节点,不然他也 ...

  6. 算法导论读书笔记-第十九章-斐波那契堆

    算法导论第19章--斐波那契堆 可合并(最小)堆(mergeable min-heap) : 支持以下5种操作的一种数据结构, 其中每一个元素都有一个关键字: MAKE-HEAP(): 创建和返回一个 ...

  7. matlab 斐波那契数列Fibonacci Sequence

    斐波那契数列Fibonacci Sequence 主代码 %% 清理可能存在的旧数据 clc; % 清屏 clear; % 清除变量 close; % 关闭可能存在的窗口 %% 调用主要代码 n = ...

  8. 算法导论之斐波那契堆

    斐波那契堆,和二项堆类似,也是由一组最小堆有序的树构成.注意区别,不是二项树,是有根而无序的树.导论中,斐波那契堆只是具有理论上的意义,是以平摊分析为指导思想来设计的数据结构,主要是渐进时间界比二项堆 ...

  9. JavaScript实现以数组形式返回斐波那契数列fibonacci算法(附完整源码)

    JavaScript实现以数组形式返回斐波那契数列fibonacci算法(附完整源码) fibonacci.js完整源代码 fibonacci.js完整源代码 export default funct ...

  10. boost::graph模块实现斐波那契堆的测试程序

    boost::graph模块实现斐波那契堆的测试程序 实现功能 C++实现代码 实现功能 boost::graph模块实现斐波那契堆的测试程序 C++实现代码 #include <boost/c ...

最新文章

  1. 字符串—NSString
  2. go语言学习---使用os.Args获取简单参数(命令行解析)
  3. 理解线程的挂起,sleep还有阻塞
  4. 第一百三十三期:MySQL锁会不会,你就差看一看咯
  5. 解决公司服务器加入域中不能启动应用系统的问题
  6. DIV+CSS如何让文字垂直居中
  7. Reachability
  8. ant里面table嵌套子表格_ElementUI el-table行内编辑验证,动态增减行
  9. java json.stringify_浅谈 JSON.stringify 方法
  10. 使用spss求标准化的线性回归方程
  11. 段码液晶屏实现原理和需要注意的事项
  12. Silverlight/Windows8/WPF/WP7/HTML5周学习导读(9月10日-9月16日)
  13. 负载均衡器之F5和Nginx
  14. Smplayer命令行模式下的用法
  15. numpy 查找 返回索引_numpy中实现ndarray数组返回符合特定条件的索引方法
  16. AWS DynamoDB的简介与使用
  17. 谷歌拼音输入法的安全漏洞
  18. 位运算符:按位取反(~)、位与()、位或(|)、位异或(^);左移运算符(<<)、右移运算符(>>)
  19. 动手深度学习——Pytorch 入门语法一
  20. 第五人格共研服务器维护中,第五人格共研服更新公告 角色调整内容详解

热门文章

  1. 【语义分割】--SegNet理解
  2. 趋肤效应实验报告_GB/T 4857.2
  3. 关于sublime出现PyV8binary错误
  4. 转换azw3到epub
  5. Java学习需要多久?程序员学习指南
  6. 画出清明上河图的代码_【高清】清明上河图(代码)
  7. 如何安装pygame模块
  8. 扩展欧几里得算法的证明
  9. 工程图字体宋体仿宋_电纸书kindle字体推荐——耐看、悦目、经典
  10. Visio 安装后提示 Dos 共享冲突