左偏树&二项堆&斐波那契堆

左偏树

定义

左偏树(leftist tree 或 leftist heap),又被成为左倾堆、左偏堆,最左堆等。

左倾树是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针外,还有两个属性:键值零距离
(01) 键值的作用是来比较节点的大小,从而对节点进行排序。
(02) 零距离(英文名NPL,即Null Path Length)则是从一个节点到一个"最近的不满节点"的路径长度。不满节点是指该该节点的左右孩子至少有有一个为NULL。叶节点的NPL为0,NULL节点的NPL为-1。

[性质1] 节点的键值小于或等于它的左右子节点的键值。
[性质2] 节点的左孩子的NPL >= 右孩子的NPL。
[性质3] 节点的NPL = 它的右孩子的NPL + 1。
定理:若左偏树有 NNN 个节点,则根节点的零距离(即最右路径长度)不超过 log2(N1)+1\lfloor\log_2(N-1)\rfloor+1

合并

对于合并函数 merge(A,B)\mathrm{merge}(A, B)merge(A,B) ,合并两个左偏堆 AAABBB

第一步有两种情况:

  1. AAABBB 中任意一个为空,就返回另一个树。

  2. 合并根节点较小树(这边是小根堆)的右子树和 BBB

如下图,我们需要合并这两个左偏堆,分别是 A,BA,BA,B ,其中 L1L1L1 , R1R1R1 , L2L2L2 , R2R2R2 为子树

a→data>b→dataa\rightarrow data>b\rightarrow dataadata>bdata ,为了满足左偏堆的性质, aaa 节点一定是 bbb 节点的父亲,所以如下合并 AAA 的右子树 R1R1R1BBB

反之亦然

第二步:

更新NPL,若不满足左偏性质,交换左右子树。

代码如下

leftist_heap merge(leftist_heap &heap1, leftist_heap &heap2)
{
    if(heap1==NULL||heap2==NULL)//情况1
        return heap1==NULL? heap2:heap1;

    if(heap1->data>heap2->data)//为了更好操作,我们这里直接交换
        swap(heap1, heap2);

    heap1->right_child=merge(heap1->right_child, heap2);//递归合并右子树
    if(!heap1->left_child||heap1->right_child->NPL>heap1->left_child->NPL)
        swap(heap1->left_child, heap1->right_child); //交换 

    heap1->pushup();//更新dist

    return heap1;
}

可以发现我们所有合并操作都是在最右路径上操作,又因为上面的定理,所以左偏堆合并的效率为 O(log2n)\mathrm{O}(\log_2n)

其他操作

插入,即看成和一个单个节点的树合并。

删除最小值,即删除根节点,然后左右子树合并。


斐波那契堆

二项树

二项树有两个定义:

第一种是:

  1. 度为 000 的二项树只包含一个节点。
  2. 度为 k(k>0)k(k\gt0)k(k>0) 的二项树有 kkk 个儿子,分别是度为 k−1,k−2,…,1,0k-1,k-2,\dots,1,0k1,k2,,1,0 的二项树

也可以使用第二种:

  1. 度为 000 的二项树只包含一个节点。
  2. 度为 kkk 的二项树由两个度为 k−1k-1k1 的树组成,其中一个是另一个的根的最左儿子。

如图:

二项树的性质:

1.该树含有2k2^k个节点。
2.树的高度是 kkk
3.在深度 ddd 中含有 C(k,d)C(k,d)C(k,d) 个节点。
4.树的根节点的度为 kkk ,其它节点的度都小于 kkk

二项堆

二项堆是二项树的集合,其中能满足以下条件:

  1. 每一个节点都都有一个关键字,满足孩子关键字大于(或小于)根节点的关键字。

  2. 每个二项树度数互不相同

如图:这是一个小根二项堆

二项堆合并:

如图,合并两个二项堆:

第一步:合并成一个列表并升序排序。

合并相同的两个树

以此类推

两棵二项树怎么合并?

由二项树定义可得,两个相同的二项树可以合并成一个更大的二项树,其中一个根节点是新树的根节点,一个是原树的根节点,一个是最左儿子,参考堆的定义

插入不说

删除即将需要删除的节点移到根节点位置,然后删除,合并所有孩子和原来的堆

其他可以参考删除

斐波那契堆

斐波那契堆(Fibonacci heap)是堆中一种,它和二项堆一样,也是一种可合并堆;可用于实现合并优先队列。斐波那契堆比二项堆具有更好的平摊分析性能,它的合并操作的时间复杂度是O(1)。 与二项堆一样,它也是由一组堆最小有序树组成,并且是一种可合并堆。 与二项堆不同的是,斐波那契堆中的树不一定是二项树;而且二项堆中的树是有序排列的,但是斐波那契堆中的树都是有根而无序的。

斐波那契堆的每个节点需记录其关键字、度数(子节点数)、左兄弟、右兄弟、第一个孩子节点、父节点,还有marked标记(marked在删除节点时有用)。
而对于一个斐波那契堆,则需记录堆中节点的总数、最大度、最小节点(某个最小堆的根节点)。
由于堆中的树没有规定的形状,在极端情况下,堆中每个元素都是一棵单独的树。这种灵活性使得一些操作可以以“偷懒”的方式来执行,而“剩下”的工作将推迟到后面的操作中来完成。比如堆的合并仅仅将由树所组成的链表链接起来,而降低元素值(decrease key)有时直接从父结点中剪断而形成一棵新树。

抽取最小结点的操作是斐波那契堆中较复杂的操作。该操作有以下两个步骤:
(1)将要抽取最小结点的子树都直接串联在根表中;
(2)合并所有degree相等的树,直到没有相等的degree的树。

而改变节点值则是斐波那契堆中最复杂的操作。以减小元素值为例,假设一个结点P的元素值为A,现在要将其降低为B,其父结点的值为C,则遵循这样的规则:
1、若B>=C, 则直接将A替换为B即可
2、若B<C, 结点P的父结点是根结点,则直接将结点P从根结点处剪断,形成一棵新树
3、若B<C, 结点P的父结点不是根结点,则将结点P从父结点处剪断,形成一棵新树,然后将父结点设置为marked
4、若B<C, 并且父结点已经被marked了,则不仅要将结点P从父结点处剪断,而且依次往上递归将被marked的父结点也从其父结点处剪断,直到碰到一个父结点没有被marked,或者碰到根结点为止

这样,我们运用斐波那契堆,求最小值(find-mininum), 插入(insert), 降低元素值(decrease-key)和合并(merge/union)可以便在O(1)\mathrm{O}(1)时间内完成。删除(delete)和删除最小值(delete minimun)可以在O(log2n)\mathrm{O}(\log_2n)均摊时间内完成。


附表:


声明:本文大部分参考自左偏堆&斐波那契堆斐波那契堆(一)之 图文解析 和 C语言的实现斐波那契堆

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章