• 算法导论笔记:19斐波那契堆


           可合并堆是支持以下5种操作的数据结构,其中每个元素都有一个关键字:

           MAKE-HEAP():创建和返回一个新的不含任何元素的堆。

           INSERT(H, x):将一个已填入关键字的元素x插入堆H中。

           MINIMUM(H):返回一个指向堆H中具有最小关键字元素的指针。

           EXTRACT-MIN(H):从堆H中删除最小关键字的元素,并返回指向该元素的指针。

           UNION(H1,H2):创建并返回一个包含堆H1和堆H2中所有元素的新堆。堆H1和H2被销毁。

     

           斐波那契堆支持可合并堆的操作,而且还支持下面的两种操作:

           DECREASE-KEY(H, x, k):将堆H中元素x的关键字赋予新值k。假定新值k不大于当前的关键字。

           DELETE(H, x):从堆H中删除元素x。

     

           斐波那契堆的时间复杂度与普通的堆相比性能如下:

           斐波那契堆对于操作INSERT, UNION和DECREASE-KEY,比起普通的堆有更好的时间性能。注意,上图中,斐波那契堆的时间是摊还时间。

     

           理论上看,如果EXTRACT-MIN和DELETE操作的数目相比于其他操作小得多的时候,斐波那契堆尤其适用,这种情形出现在很多应用中:一些问题(计算最小生成树和寻找单源最短路径)中必不可少的用到斐波那契堆。

           但是实际上,除了需要管理大量数据的应用外,对于大多数应用,斐波那契堆的常数因子和编程复杂性使得它比普通二项堆并不那么适用。

           斐波那契堆对于SEARCH操作的支持比较低效,所以,设计给定元素的操作均需要一个指针指向这个元素。

     

    一:斐波那契堆结构

           一个斐波那契堆是一系列具有最小堆序的有根数的集合。也就是堆中的每棵树都满足最小堆性质:每个节点的关键字大于或等于父节点的关键字。如下图就是一个例子:

           每个节点x包含一个指向父节点的指针x.p。一个指向它的某一个孩子的指针x.child。x的所有孩子被连接成一个环形的双向链表,称为x的孩子链表。孩子链表中的每个孩子y均有指针y.lefty.right,分别指向y的左兄弟和右兄弟。如果y是仅有的孩子,则y.left=y.right=y。孩子链表中各兄弟出现的次序是任意的。

     

           使用双向链表可以在O(1)时间内在任何位置插入或者删除一个节点,而且也可以在O(1)时间内将两个这样的链表连接成一个循环链表。

           每个节点还有两个属性:节点x的孩子链表中的孩子数目为x.degreex.mark表示x自从上一次成为某个节点的孩子后,是否失去过孩子。

           通过指针H.min访问给定的斐波那契堆H。该指针指向具有最小关键字的节点。如果不止一个节点具有最小关键字,则任意一个就可以成为最小节点。如果H.min = NULL,则该斐波那契堆是空的。

           在斐波那契堆中,所有树的根节点,通过其left和right指针也形成一个环形的双向链表。称为根链表根链表中,树的次序可以使任意的。

           H.n表示当前堆H中节点的数目。

           下图是一个包含属性细节的斐波那契堆,后续称斐波那契堆为F堆:

           在F堆中,对于摊还分析均假定,在一个n个节点的F堆中,任何节点的最大度数都有上界D(n)。其中D(n)<=O(lg n)

     

    二:可合并堆操作

           F堆上的一些可合并堆操作要尽可能的延后执行。不同的操作可以进行性能平衡。比如:通过将一个新节点加入根链表的方式来插入一个节点。如果从空的F堆开始,插入k个节点,则F堆将是一个正好包含k的节点的双向循环链表。

           此时,如果在F堆H上执行一个EXTRACT-MIN操作,在移除H.min后,需要遍历根链表中剩下的k-1个节点找出新的最小节点。这里便存在性能平衡问题。只要遍历整个根链表,并且把节点合并到最小堆序树中以减小根链表的规模。执行完该操作后,根链表中的每个节点与其他节点的度数均不同。

     

    1:创建一个新的F堆

           MAKE-FIB-HEAP创建新的F堆,其中H.n=0,H.min=NULL。MAKE-FIB-HEAP的摊还代价等于实际代价O(1)。

     

    2:插入一个节点FIB-HEAP-INSERT,就是将新节点插入到根链表中。该操作的摊还代价与实际代价都是O(1):

          

    3:寻找最小节点

           F堆的最小节点可以通过H.min得到。因此,寻找最小节点的实际代价和摊还代价都是O(1)。

     

    4:两个F堆的合并

           合并两个F堆H1和H2,简单的将H1H2的根链表链接,然后找到新的最小节点即可,之后H1和H2将被销毁。合并操作FIB-HEAP-UNION的摊还代价和实际代价都是O(1):

     

    5:抽取最小节点

           抽取最小节点操作,包括合并树的延后工作。代码中假设一个节点z被从根链表中移除之后,留在根链表中的指针更新,但是z的指针并未改变。

           该算法将最小节点z的每个孩子都变为根节点,并从根链表中删除最小节点。然后通过CONSOLIDATE操作将根链表中,具有相同度数的节点合并,直到根链表中的每个节点都具有不同的度数。

           这里要注意的是,执行完第6行之后,虽然z被从根链表中移除了,但是z的指针保持不变,如果此时z=z.right的话,则说明z是根链表中仅有的一个节点,而且没有孩子节点。否则,z.right会指向根链表中的其他节点,或者是原来的孩子节点(因为孩子节点已经上升到根链表中了)。

     

           CONSOLIDATE操作减少F堆中树的数目,将根链表中度数相同的节点进行合并。其中要用到辅助数组A[0..D(H.n)],如果A[i]=y,则表示度数为i的节点为y算法如下:

           在CONSOLIDATE算法中,首先初始化数组A[0..D(n)]为NULL,然后依次遍历根链表中的每个节点x,如果碰到A[d] != NULL的情况,说明,之前具有与x相同度的节点y,根据x和y的关键字大小,将x与y进行合并,使y成为x的孩子。这样x的度数就增加了1,然后继续处理,直到A[d]为空。最终,使得A[d]=x。这样处理后续根节点时,x是已处理过的根节点中具有度数为d的唯一节点。FIB-HEAP-EXTRACT-MIN的摊还代价为O(D(n)) = O(lg n)。

     

    三:关键字减值和删除一个节点

    1:关键字减值

          

           x的关键字变小以后,如果违反了最小堆的性质,首先进行“切断”,直接将x切断与其父节点y之间的联系,使x上升到根链表中,使其成为根节点。如果x的父节点y已经失去了两个孩子(失去第一个孩子后,y.mark=TRUE),则需要向上级联切割。这主要为了保证最大度数的界为O(lg n)。FIB-HEAP-DECREASE-KEY的摊还代价为O(1)。

     

    2:删除一个节点

          

           删除一个节点的摊还代价为O(D(n)) = O(lg n)

           

  • 相关阅读:
    在线|九月月考选填题
    函数$f(x)=e^xpm e^{-x}$相关
    偶函数性质的推广
    2020年全国卷Ⅱ卷文科数学选填题解析版
    2020年全国卷Ⅱ卷文科数学解答题解析版
    待定系数法
    特殊方法求函数解析式
    phd文献阅读日志-4.1
    phd文献阅读日志-1.2~3.2(1.2,2.1,2.2,3.1,3.2)
    完美解决linux下vim在终端不能用鼠标复制的问题
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247225.html
Copyright © 2020-2023  润新知