左偏树——可并堆

来源:互联网 发布:君何以知燕王句式 编辑:程序博客网 时间:2024/06/07 19:55

这一周的任务就是熟悉一下这种算法

看了一下学姐的blog,感觉还是有一定必要了解一下这种算法的:

论文链接
实际上论文已经很详细了,但是还是在这里废话一下(介绍一点简单的内容)

引入

可并堆,顾名思义就是可以合并的堆
我们一般概念上的堆就是一种优先队列
可是如果要求把两个堆合并,要怎么办呢?
这时我们就需要一种新的数据结构:

可并堆(Mergeable Heap)
是一种抽象数据类型,它除了支持优先队列的三个基本操作(Insert, Minimum, Delete-Min),还支持一个额外的操作——合并操作:
H ← Merge(H1,H2)
Merge( ) 构造并返回一个包含H1和H2所有元素的新堆H。

上面是来自《算法合集之<左偏树的特点及其应用>》 的定义
看起来是一种很优美的数据结构

基本描述

但是至今为止我们都停留在定义上,连左偏树长什么样子都不知道,所以我们来一段标准描述:

左偏树(Leftist Tree)是一种可并堆的实现。
左偏树是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针( left, right )外,还有两个属性:键值和距离(dist)
键值上面已经说过,是用于比较节点的大小。
距离则是如下定义的:
节点i称为外节点(external node),当且仅当节点i的左子树或右子树为空 ( left(i) = NULL right(i) = NULL );
节点i的距离(dist(i))是结点i到它的后代中,最近的外节点所经过的边数。特别的,如果节点i本身是外节点,则它的距离为0;
而空节点的距离规定为-1 (dist(NULL) = -1)。

看起来好像有点懵
我们翻译成中文看一下:

性质1:左偏树,满足堆性质

简单来说,姜还是老的辣,权值还是老爹大。。 (骚话)
之所以不同于普通的堆,ta还有一些特殊的性质

性质2左儿子的距离不小于右儿子的距离

注意这里距离的定义:
结点i到它的后代中,最近的外节点所经过的边数

由这两条性质,我们可以得出左偏树的定义:左偏树是具有左偏性质的堆有序二叉树

这就是一棵优美的左偏树(图片来自chao_yu):
这里写图片描述

其中蓝色的标示就是每个节点的dis

性质及引理

前面我们已经介绍了左偏树的两种性质
下面我们来看一下另外的重要性质

我们知道,一个节点必须经由它的子节点才能到达外节点
由于性质2,一个节点的距离实际上就是这个节点一直沿它的右边到达一个外节点所经过的边数(因为dis是对最小距离的定义,在左偏树中右子树的dis一定比左子树小),也就是说,我们有

性质3: 节点的距离等于它的右子节点的距离加1

dist( i ) = dist( right( i ) ) + 1
外节点的距离为0,由于性质2,它的右子节点必为空节点
为了满足性质3,故前面规定空节点的距离为-1

下面我们来讨论左偏树的距离和节点数的关系

引理1:若左偏树的距离为一定值,则节点数最少的左偏树是完全二叉树

证明:由性质2可知,当且仅当对于一棵左偏树中的每个节点i,都有 dist(left(i)) = dist(right(i))时,该左偏树的节点数最少。显然具有这样性质的二叉树是完全二叉树

定理1:若一棵左偏树的距离为k,则这棵左偏树至少有2^(k+1)-1个节点。
证明:由引理1可知,当这样的左偏树节点数最少的时候,是一棵完全二叉树。距离为k的完全二叉树高度也为k,节点数为2^(k+1)-1,所以距离为k的左偏树至少有2^(k+1)-1个节点。

作为定理1的推论,我们有:

性质4:一棵N个节点的左偏树距离最多为ëlog(N+1)û -1

证明:设一棵N个节点的左偏树距离为k,由定理1可知,N ≥ 2^(k+1)-1,因此k ≤ ëlog(N+1)û -1。

操作

合并

(文段摘自原论文)

C ← Merge(A,B)

Merge( ) 把A,B两棵左偏树合并,返回一棵新的左偏树C,包含A和B中的所有元素。在本文中,一棵左偏树用它的根节点的指针表示。

在合并操作中,最简单的情况是其中一棵树为空(也就是,该树根节点指针为NULL)。这时我们只须要返回另一棵树。

若A和B都非空,我们假设A的根节点小于等于B的根节点(否则交换A,B),把A的根节点作为新树C的根节点,剩下的事就是合并A的右子树right(A) 和B了。

right(A) ← Merge(right(A), B)

合并了right(A) 和B之后,right(A) 的距离可能会变大,当right(A) 的距离大于left(A) 的距离时,左偏树的性质2会被破坏。在这种情况下,我们只须要交换left(A) 和right(A)。

若dist(left(A)) > dist(right(A)),交换left(A) 和right(A)


最后,由于right(A) 的距离可能发生改变,我们必须更新A的距离:

dist(A) ← dist(right(A)) + 1

不难验证,经这样合并后的树C符合性质1和性质2,因此是一棵左偏树。至此左偏树的合并就完成了。

我们直接用图来说明:
(图片来自原论文)
这里写图片描述

(图片来自chao_yu)
这里写图片描述
这里写图片描述
这里写图片描述

插入新节点

单节点的树一定是左偏树,因此向左偏树插入一个节点可以看作是对两棵左偏树的合并
由于合并的其中一棵树只有一个节点,因此插入新节点操作的时间复杂度是O(logn)

删除最小节点

由性质1,我们知道,左偏树的根节点是最小节点
在删除根节点后,剩下的两棵子树都是左偏树,需要把ta们合并
由于删除最小节点后只需进行一次合并,因此删除最小节点的时间复杂度也为O(logn)

左偏树的构建

算法一 暴力算法——逐个节点插入,时间复杂度为O(nlogn)。

算法二 仿照二叉堆的构建算法,我们可以得到下面这种算法:

Ø 将n个节点(每个节点作为一棵左偏树)放入先进先出队列

Ø 不断地从队首取出两棵左偏树,将它们合并之后加入队尾

Ø 当队列中只剩下一棵左偏树时,算法结束

例题

原创粉丝点击