斐波那契堆的定义

参看19章 二项堆我们可以看到对于可合并堆操作,二项堆均有O(lgn)的时间,对于本章将要讨论的斐波那契堆也支持这些操作,而且它有着更好的渐进时间界,对于不涉及元素删除的操作,它有着O(1)的时间界。

和二项堆一样,在仅支持可合并对操作(这些操作请参考19章)的情况下,每个斐波那契堆也是由一组二项树组成,只不过这些树均是无序的。无序二项树的定义和二项树类似:无序二项树U0只包含一个节点,一棵Uk的无序二项树包含两棵Uk-1的无序二项树,且其中一棵是另外一棵的任意一个孩子节点

在19章列出的四个性质中,无序二项树依然满足,只是第四个性质稍作改变,如下:对无序二项树Uk,根的度数为k,它大于任何子节点的度数,根的子女以某种顺序分别成为子树U0,U1...Uk-1的根。并不是按度数递减排列的。

当然,在支持decreaKey和delete操作的斐波那契堆中,在某些时刻,堆中的树并不是无序二项树。

斐波那契堆的结构

我们先抛出一张图,直观地看看斐波那契堆是啥样子的。很显然,下图中的堆中的树就不全是无序二项树。

可以看出,整个根表是一个双向循环链表,对于每个根,是一棵无序二项树(可能),子节点也被链成双向循环链表,可以看作一个子堆。C++代码的斐波那契堆节点的结构如下:

template <typename Key,typename Value>
struct fibonacci_heap_node
{//斐波那契堆节点类型Key key;Value value;bool mark = false;//标志在上次清除标记后是否失去过一个孩子,初始时均为falsesize_t degree = 0;fibonacci_heap_node *parent = nullptr;fibonacci_heap_node *prev = nullptr;fibonacci_heap_node *next = nullptr;fibonacci_heap_node *child = nullptr;fibonacci_heap_node(const Key &k, const Value &v) :key(k), value(v){}void print(){printf("key: %-6d value: %-6d degree: %-6d\n", key, value, degree);}
};

斐波那契堆的操作

insert,minimum,delete操作很简单,就不细说了。下面只是粗略地讨论各操作流程,操作的代码将在后面给出,含有更详细的注释。

Union操作。相对于二项堆的合并,斐波那契堆的合并操作很简单,就是两个双向循环链表——双向环——的链接,下面的图解表明了一种链接方式,时间O(1)。

extractMin操作。流程如下:

1、记录下最小节点,即head所指节点;然后将其每一个孩子链接到根表,期间设置parent域为空;

2、重置head域。若整个堆中仅剩下一个元素,则设置其为空;否则将其随意指向一个节点,然后开始堆修正操作consolidate,合并那些度相同的节点。

3、返回head,结束。

consolidate子操作,目的是防止堆过宽。流程如下:

1、扫描每一个根表中的节点,将其地址存入一个数组中,按节点的度索引;

2、若碰到某节点的度所对应的数组项不为空,且不是同一节点,则说明存在两棵度相等的不同的树,开始合并;

3、将关键字大的节点链为关键字小的孩子,然后自增度,扫描下一个索引项,继续合并;

4、重复1~3,直至每一个根表节点均扫描过;

5、合并结束后,所有存在于根表的节点的指针按度索引存储于数组中,扫描数组,确定head的最终位置,结束。

该操作的终止条件从步骤2即可看出,若为同一节点,表明已经循环整个链表一周,应当终止了。

decreaseKey操作,其中的剪枝是为了防止堆过深,mark的作用就在于此,辅助剪枝。流程如下:

1、减小该节点的关键字大小,若新关键字较大,则退出;

2、若更改后节点的关键字比父节点大,则需要剪枝了,将其从父节点孩子位置剪下来,链到根表中;

3、级联判断父节点是否也该被剪枝,被剪条件是——算上这个孩子,它已经失去过两个孩子,那么应当剪掉;

4、重复上述过程,直至遇到根表节点或者不满足步骤3条件。

下面本章整个斐波那契堆的实现代码,注释详细,经过一些测试,运行正确,欢迎讨论。

//斐波那契堆,默认最小堆#include<iostream>
#include<cmath>
#include<vector>using namespace std;template <typename Key, typename Value>
struct fibonacci_heap_node
{//斐波那契堆节点类型Key key;Value value;bool mark = false;//标志是否曾经失去过一个孩子,初始时均为falsesize_t degree = 0;fibonacci_heap_node *parent = nullptr;fibonacci_heap_node *prev = nullptr;fibonacci_heap_node *next = nullptr;fibonacci_heap_node *child = nullptr;fibonacci_heap_node(const Key &k, const Value &v) :key(k), value(v){}void print(){ printf("key: %-6d value: %-6d degree: %-6d\n", key, value, degree); }
};template <typename Key, typename Value, typename Compare = less<Key>>
class fibonacci_heap
{//斐波那契堆
public:typedef fibonacci_heap_node<Key, Value>                        node;typedef Key                                                    key_type;typedef Value                                              value_type;
private:node *head;//永远指向最小值节点Compare compare;//比较器size_t n = 0;//节点总数
private:void linkNode(node *&lhs, node *&rhs){//将lhs所指节点链接为rhs所指节点的前驱if (rhs == nullptr){//若rhs为空rhs = lhs;lhs->next = lhs;lhs->prev = lhs;}else{//否则lhs->next = rhs;rhs->prev->next = lhs;lhs->prev = rhs->prev;rhs->prev = lhs;}}void removeNode(node *p){//将节点p从所属的双链表中移除掉if (p->next == p){//若该双链表仅有一个节点p->next = nullptr;p->prev = nullptr;}else{//否则p->prev->next = p->next;p->next->prev = p->prev;}}void heapLink(node *big, node *small){//将根节点关键字较大的树链为根关键字较小的树的孩子removeNode(big);//首先移除biglinkNode(big, small->child);//再链为其孩子++small->degree;big->parent = small;big->mark = false;//1、成为一个节点的孩子时,清除标记位}void prune(node *p, node *par){//剪枝,将树根为p的子树从父节点par剪掉if (par->degree == 1) par->child = nullptr;//可能需要重新设置孩子else if (par->child == p) par->child = p->next;removeNode(p);linkNode(p, head);//链到根表中p->parent = nullptr;p->mark = false;//2、成为一棵新树时,清除标记位--par->degree;}void removeChildsToRoot(node *p){//将树根p下的孩子们全部移到根表中if (p->child != nullptr){node *first = p->child, *last = p->child->prev;while (true){//迭代,将p的每个孩子链到根表中node *curr = first;first = first->next;removeNode(curr);linkNode(curr, head);curr->parent = nullptr;if (curr == last) break;//若已经处理完最后一个,跳出循环}}}void cascadingPrune(node*);//级联剪枝void consolidate();//合并根表void print_aux(node*)const;void destroy(node*);
public:fibonacci_heap(node *h = nullptr, const Compare &c = Compare()) :head(h), compare(c){}fibonacci_heap(const Compare &c) :head(nullptr), compare(c){}node* insert(const Key&, const Value&);node* minimum()const { return head; }void FibHeapUnion(fibonacci_heap&);pair<Key, Value> extractMin();void decreaseKey(node*, const Key&);void erase(node*);bool empty()const { return head == nullptr; }size_t size()const { return n; }void print()const { print_aux(head); }~fibonacci_heap(){ destroy(head); }
};template <typename Key, typename Value, typename Compare>
inline fibonacci_heap_node<Key, Value>*
fibonacci_heap<Key, Value, Compare>::insert(const Key &k, const Value &v)
{//插入一个节点node *p = new node(k, v);linkNode(p, head);//直接插到根表中if (compare(p->key, head->key))head = p;++n;return p;
}template <typename Key, typename Value, typename Compare>
inline void fibonacci_heap<Key, Value, Compare>::FibHeapUnion(fibonacci_heap &rhs)
{//合并两个斐波那契堆if (rhs.empty())return;//若被合并堆空if (empty()){//若本堆空swap(head, rhs.head);swap(n, rhs.n);swap(compare, rhs.compare);return;}//链接两个双链表node *head_prev = head->prev;head_prev->next = rhs.head->prev;rhs.head->prev->prev->next = head;head->prev = rhs.head->prev->prev;rhs.head->prev->prev = head_prev;if (compare(rhs.head->key, head->key))head = rhs.head;n += rhs.n;rhs.head = nullptr; rhs.n = 0;
}template <typename Key, typename Value, typename Compare>
pair<Key, Value> fibonacci_heap<Key, Value, Compare>::extractMin()
{//抽取堆最小值node *p = head;//记下最小值节点removeChildsToRoot(head);head->child = nullptr;removeNode(head);//从根表中移除headif (head->next == nullptr) head = nullptr;else//重新设置head{//且摘除该最小值节点后,对根表中的树进行合并head = head->next;consolidate();}--n;pair<Key, Value> tmp = pair<Key, Value>(p->key, p->value);//返回值delete p;return tmp;
}template <typename Key, typename Value, typename Compare>
void fibonacci_heap<Key, Value, Compare>::consolidate()
{//合并堆中,即根表中,度数相同的树size_t max_degree = static_cast<size_t>(log(n) / log(2));vector<node*> temp(max_degree + 1);//存储各度数的树的根node *first = head;while (true){//不断迭代,扫描每一棵树node *small = first;first = first->next;if (small->degree > max_degree){//max_degree是指在合并后堆中的树的最大度数。但是在合并之前,可能出现某树的度数超过的情况。//这是因为发生一系列剪枝(但没有剪掉某树根的孩子),抽取(也没有抽取该树的节点)操作后,//节点数目减少,使得计算出来的最大度数较之前小,而此时该树的度依然维持在之前的水平,此时//就会发生这种情况,这在第23章实现prim算法时出现过,代码后附上当时的堆结构图。因而在此需要//将其所有孩子移到根表中,减少该树的度。removeChildsToRoot(small);small->degree = 0;}size_t d = small->degree;if (small == temp[d]) break;//当前将要处理的树已存在于temp中,则说明已经合并完毕,退出while (temp[d] != nullptr){//若temp中有另外一棵和当前树的度数相同的树node *big = temp[d];if (!compare(small->key, big->key))//若当前树的根关键字较大(最小堆时)swap(small, big);//则交换//这个错误调试了好久,将要被合并的树恰巧是first所指向的,而且要放在swap之后,第一次排除//错误放在swap前面,后来在MST算法中,又出错,应该要放在这里,因为big与small可能会发生交换if (big == first) first = first->next;heapLink(big, small);//将big树链为small的孩子temp[d] = nullptr;++d;//生成了度增1的树,继续合并}temp[d] = small;//若不存在,则设置temp的相应槽位}head = nullptr;for (size_t i = 0; i != temp.size(); ++i){//扫描tempif (temp[i] != nullptr && (head == nullptr || compare(temp[i]->key, head->key)))head = temp[i];//设置新的head,即最小值节点}
}template <typename Key, typename Value, typename Compare>
void fibonacci_heap<Key, Value, Compare>::decreaseKey(node *p, const Key &k)
{//减小某一节点关键字if (!compare(k, p->key)){//若新关键字较大cerr << "greater key" << endl;return;}p->key = k;node *par = p->parent;if (par != nullptr && compare(p->key, par->key)){//若新关键字比父节点关键字小prune(p, par);//则剪掉以p为根的树,使其成为根表中一员cascadingPrune(par);//并级联剪枝父节点}if (compare(p->key, head->key))//测试是否需要重新设置headhead = p;
}template <typename Key, typename Value, typename Compare>
void fibonacci_heap<Key, Value, Compare>::cascadingPrune(node *p)
{//级联剪枝node *par = p->parent;if (par != nullptr){//若p的父节点存在//若在此之前p没有失去孩子,言下之意是p从上次清除标记后到现在仅失去过一个孩子if (p->mark == false) p->mark = true;//则将其标为true,表明失去一个孩子else{//若现在失去的使其第二个孩子prune(p, par);//则将其剪枝p->mark = false;//3、清除标记位cascadingPrune(par);}}
}template <typename Key, typename Value, typename Compare>
void fibonacci_heap<Key, Value, Compare>::erase(node *p)
{//删除某一节点node *p_min = minimum();decreaseKey(p, p_min->key - 1);extractMin();
}template <typename Key, typename Value, typename Compare>
void fibonacci_heap<Key, Value, Compare>::print_aux(node *p)const
{//递归打印堆if (p == nullptr) return;node *first = p->next;while (true){node *curr = first;first = first->next;print_aux(curr->child);curr->print();if (curr == p) break;//表明已经绕地球一圈,该结束了}
}template <typename Key, typename Value, typename Compare>
void fibonacci_heap<Key, Value, Compare>::destroy(node *p)
{//销毁堆if (p == nullptr) return;node *first = p->next;while (true){node *curr = first;first = first->next;destroy(curr->child);--n;if (curr == p){delete curr;break;}else delete curr;}
}int main()
{fibonacci_heap<int, int> fh1,fh2;for (int i = 0; i != 10; ++i){if (i % 2 == 0) fh1.insert(i, 2 * i);else fh2.insert(i, 2 * i);}cout << "fh1" << endl;fh1.print(); cout << fh1.size() << endl;cout << "fh2" << endl;fh2.print(); cout << fh2.size() << endl;fh1.FibHeapUnion(fh2);cout << "union" << endl;fh1.print(); cout << fh1.size() << endl;while (!fh1.empty()){cout << "-------------" << endl;fh1.minimum()->print();cout << endl;fh1.extractMin();fh1.print();}getchar();return 0;
}

关于上述代码需要注意的几点:

1、consolidate函数中,first可能绕过双向循环链表,指向了已存在于数组中的根,而且正好是将要被合并的树(big所指向的)的根,此时需要将first继续向前移动;

2、consolidate函数第一层while循环的终止条件,即small和temp[small->degree]指向了同一棵树,表明该合并的树已经合并完毕;

3、三种情况下将会清除标记位:a.成为一个节点的孩子;b.成为根表一员,即成为一棵树;c.失去两个孩子后,被剪枝成为根表一员;

4、级联剪枝发生在该节点已经失去过两个孩子时发生;

5、为什么不直接向上调整呢?我的理解是,这样的话decreaseKey平摊时间将不再是O(1),而是O(lgn)。

6、consolidate函数中出现过的某树度数超过最大度数的情况下堆的结构图,在第23章 最小生成树算法 斐波那契堆实现中出现的。可以看出,max_degree应当为2,可是此时这棵树的度却为3,节点中的数字是顶点的编号,节点的键是该顶点和MST的距离。

思考题 20-1

1、将x的孩子双向链表摘除链接到根表在O(1)时间是可以实现的,但是每个孩子都有一个父指针parent,对它们的修改只能迭代,股时间应该是O(degree[x]);

2、O(c + degree[x]);

3、分析两者的代码,最好展开fib-delete,可以看出,两者基本一样,没有太大的区别;在x != min[H]的前提下,fib-delete只是多了一点无关紧要的判断语句,并不影响渐进时间,故pisano-delete并不具有优越的渐进运行时间。

思考题 20-2

1、k = key[x]时,不变;

k < key[x]时,就是decreaseKey;

k > key[x]时,将其与孩子节点内容不断交换,直到满足最小堆性质为止,和对深度有关,一个不紧确的上界O(lgn)。

2、若是n[H]较大还好说,就是调用destroy,时间O(n);但是若是只是删除一部分节点,高效算法目前还不得,求指导。




算法导论 第20章 斐波那契堆相关推荐

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

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

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

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

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

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

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

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

  5. 算法导论--斐波那契堆

    斐波那契堆 斐波那契堆也是数据储存结构的一种,这种数据结构之所以产生,主要有两个优点:1.对于数据合并操作具有很高的效率:2.对于其他一般数据操作平均时间复杂度较好(注意这里是平均复杂度,不是最坏情形 ...

  6. c语言 兔子数列螺线图,经典算法大全51例——2.斐波那契数列(兔子数列)

    经典算法大全51例--2.斐波那契数列 算法目录合集 地址 说明 题目 原理分析 代码实现--Java 相关题目其他变形: 1.爬楼梯(来源:力扣LeetCode) 2.兔子成熟期拉长 官方题解 分析 ...

  7. 斐波那契堆(Fibonacci heaps)

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

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

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

  9. 斐波那契堆(不太详尽)

    总结:这一章讲了斐波那契堆,它是一种比二项堆更松散的堆,它由一组无序的二项树组成,对不涉及删除元素的操作,它仅需O(1)的平摊运行时间.本章介绍斐波那契堆的插入.合并.删除等操作. 1.    斐波那 ...

最新文章

  1. 基于thinkphp的省略图便捷函数
  2. Java的基础方法Java的对象_java基础之 创建对象的几种方式
  3. html文件本质上是一个,html文件是什么
  4. 怎么样才能写出出色的代码
  5. js中类型识别的方法
  6. 对于随机过程方面书籍的评论(转贴)
  7. Visual Studio下Qt调用IDL
  8. linux进入运行exe命令,在Deepin V20系统中打开运行exe文件的两种方法
  9. 与其埋头啃文献不如关注这些公众号
  10. CentOS更改root密码
  11. 清明出行之高德路况思考
  12. 为什么许多器件的片选信号低电平有效,而不是高电平有效?
  13. STM32名字含义以及其与ARM公司的关系
  14. HyperX旋火无线游戏鼠标,摆脱“线”制,黑白双煞争分夺秒
  15. 声音能否内嵌在PPT中
  16. 泛目录如何实现日收?
  17. python爬取前程无忧scrapy存mogondb案例
  18. Axure9 自建元件库+编辑
  19. mplus的java_Mplus的二阶五因子语法
  20. html网页制作期末大作业成品_网页设计期末作业-简洁源码-我的学校

热门文章

  1. 冼牛:即构实时网络调度系统如何应对跨国场景挑战
  2. Yunxion资产监测设备帮助外贸企业实时把握国际货物运输状态
  3. ViewPager 实现纵向翻页切换、仿抖音视频垂直切换
  4. Private Set Intersection(PSI)简介和资料分享
  5. windows下编译xv6
  6. html5 toggle,JQuerytoggle使用分析
  7. opera浏览器怎么打开html的控制台,Opera浏览器怎么开启预读功能
  8. 用 Vue 实现学生信息管理系统的增删改查操作,模拟数据库操作(但并没有连接数据库)
  9. 在充满不确定性的职场中,她只做了这一件事
  10. 奇数阶幻方法C语言运用指针,奇数幻方的构造方法(转载)