左偏树简介

1、左偏树

1.1定义

左偏数是一颗二叉树,并具有堆性质。左偏树具有两个属性:键值(key)和距离(dist)。

键值(key):用于节点比较大小的属性,类似于堆中节点的键值

外节点:左子树或右子树为空的节点称为外节点,左子树和右子树可以同时为空。所以叶子节点必是外节点,定义外节点的距离值为0,空节点的距离值为-1。空节点即树中不存在的节点,表示为NULL,dist(NULL)=-1

距离(dist):一个不是外节点的距离定义为其到子树中最近的外节点的距离,即两个节点之间路径的权值之和,可以理解外两个节点之间的边数。左右子树均不为空的节点不是外节点。左偏数的距离定义数中根节点的距离。左偏树的深度和距离无必然联系,一颗向左偏的链也是左偏树,其距离为0,深度为n(树的深度:根节点为第一层次,根的左右儿子为第二层次,定义树中的最大层次为树的深度或高度).如下图所示为一颗左偏树。

如上图所示,圆圈内的数值是键值key,圆圈左上角的数值是此节点的距离值,即dist值。

1.2 性质

符号如下:

T(n):一颗具有n个节点的左偏数

x:节点x

lson:节点x的左儿子节点

rson:节点x的右儿子节点

parent(x):节点x的父节点

left(x):节点x的左子树

right(x):节点y的右子树

一个树是左偏树,需满足以下基本性质

(基本性质1)节点的键值小于或等于它左右儿子的键值,也称为堆性质。即key(x)<=key(lson),且key(x)<=key(rson)。此处定义的是小根堆。由于堆性质,则左偏数取最小值的时间复杂度为O(1)

(基本性质2)每个节点的左儿子的dist值大于等于右儿子的dist值。即左偏性质。dist(lson)>=dist(rson)。

左偏树的左右儿子也是左偏树,除了两条基本性质外,还有如下性质:

(扩展性质1):任意节点,其距离等于其右儿子的距离加1。即 dist(i)=dist(rson)+1.

当一个节点距离越大时,代表其离外节点的距离越远,由于基本性质2,左儿子的距离>=右儿子的距离,所以节点到其最近的外节点是通过右儿子找到的,根据距离的定义,节点的距离等于其右儿子的距离加1

(扩展性质2)左偏树的距离是定值(大于0),则节点数最少的左偏数是完全二叉树(实为满二叉树):

当左偏数距离定值时,节点数最少的时候是每个节点x,dist[lson]=dist[rson] ,则这样的二叉树是一个完全二叉树(实为满二叉树),同时对于一颗是完全二叉树的左偏树来说存在如下关系:树的深度(高度)=树的距离+1。树的深度:根节点为第一层次,根的左右儿子为第二层次,定义树中的最大层次为树的深度(高度)。

(扩展性质3):若左偏数的距离为k,则其节点数不小于2^{k+1}-1

当一个左偏树距离是一个定值d,节点最少的时候它是一颗完全二叉树(实为满二叉树),则完全二叉树的节点数为(2^{k}-1,2^{k+1}-1],满二叉树的节点数等于2^{k+1}-1,所以节点数大于等于2^{k+1}-1

(扩展性质4):一个左偏树有n个节点,其距离不超过\left \lfloor log(n+1) \right \rfloor-1,即dist(T(n))<=\left \lfloor log(n+1) \right \rfloor-1

一颗左偏数节点数是个定值n,距离为k,由扩展性质2得到n>=2^{k+1}-1,所以k<=log(n+1)-1。当节点数是个定值,距离最大的左偏树会满足如下条件:叶子节点只在层次最大的两层上出现;设树的深度为x,则前x-1层是满二叉树

1.3 左偏树的操作

左偏树除了有堆的基本操作(插入元素、删除元素、建堆)外,还有一个它重要的操作:合并。

1.3.1 合并操作

合并左偏树x和y操作的过程如下:

(1)在x、y中取较小的值最为合并后的根,假设x的值较小,则合并后x的根为新树的根,x的左子树为新树的左子树

(2)递归地合并x的右子树和y,若合并后不满足左偏性质(dist(lson)<dist[rson]),则交换左右儿子,即swap(lson,rson);

(3)x的右子树或y 为空时,合并算法结束。

             

               

         

 

每递归一层,会有一个堆的距离减1,每递归一次,会将分解的右子树参加合并,而根据扩展性质4,一个n个节点的左偏数的距离不会超过\left \lfloor log(n+1) \right \rfloor-1,设两个左偏树的距离为n和m,所以最坏情况下,需要递归\left \lfloor log(n+1) \right \rfloor-1+\left \lfloor log(m+1) \right \rfloor-1次,所以需要的时间复杂度O(log(n+m)).伪代码如下:

def merge(A,B)
{
if (A==NULL) return B
if (B==NULL) return A
if (key(A)>key(B))
   swap(A,B)
right(A)=merge(right(A),B)
if (dist(left(A))<dist(right(A)))           //不满足左偏性质,进行交换
    swap(left(A),right(A))

if(righr(A)==NULL) dist(A)=0
dist(A)=dist(right(A))+1
return A

}

个人认为,按上述方法合并,设C=merge(A,B),则min(dist(A),dist(B))<=dist(C)<=max(dist(A),dist(B))+1。还有一种不需要交换左右儿子的做法,即将dist较大的作为作儿子,dist较小的作为右儿子 。

1.3.2 插入一个新节点

插入一个新节点,将相当于插入一颗只有一颗节点的左偏树,伪代码如下:

由于合并操作的复杂度为 O(log(n+m)),而只有一个节点左偏树的距离为0,所以操作复杂度为O(log(n))

def insert(x,B)
{
A=MakeIntoTree(x)
return merge(A,B)
}

1.3.3 删除最小节点

删除最小节点即删除根节点,删除根节点后,将左右子树合并即可得到新树.伪代码如下:

def delete(A)
{
t=key(root(A))
A=merge(left(A),reight(A))
return t
}

删除根节点后,左子树的距离为不大于\left \lfloor log(n+1) \right \rfloor-1,右子树的距离为\left \lfloor log(n+1) \right \rfloor-2,所以合并左右子树的复杂度为O(log(n))

1.3.3 左偏树的构建

左偏树的构建有两种方法,一种是逐个节点插入法,另外一种是采用合并的方法建堆。具体方法如下。

(1)逐个插入法,在节点依次为1、2、3...n-1的左偏树上,合并一个只有一个节点的左偏树,复杂度为log1+log2+...log(n)=O(nlog(n))

(2)合并法。算法步骤如下:

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

     (b)从队首取出两颗左偏数,合并后放入队尾

     (C)当队列中只有一颗左偏树时,算法结束

算法的实现过程如下图

 

   构建堆的伪代码如下:

def create(queue q)

{
while(queue.count()!=1)

{

t1=q.pop();

t2=q.pop();

t1=merge(t1,t2)

q.push(t1)
}

return q.pop()
}

第二种算法的复杂度如下:首先是对只有1节点的左偏树进行合并\frac{n}{2}次,其次对2个节点的左偏树进行合并\frac{n}{4}次,再次是4个节点的左偏树进行合并\frac{n}{8}次,...2^{i-1}个节点的左偏树合并\frac{n}{2^{i}}次,...最后对\frac{n}{2}n-\frac{n}{2}个节点(\frac{n}{2}2^{k-1}2^{k}两个整数之间取绝对值差值最小的,若n=7,\frac{n}{2}为4;n=11,\frac{n}{2}为4)的左偏树进行合并1次,所以总的复杂度为

\frac{n}{2}\times O(1)+\frac{n}{4}\times O(2)+\frac{n}{8}\times O(4)+...\frac{n}{2^{i}}\times O(2^{i-1})+...1\times O(\frac{n}{2})=O(\sum_{i=1}^{k}\frac{n}{2^{i}}\times 2^{i-1})=O(n)

1.3.4 删除任意已知节点

“已知”的含义是节点存在于左偏数中,但左偏数除了去最值外,不能有效搜索指定键值的结点。删除任意已知节点,首先要找到这个已知节点,左偏数具有堆性质,可以在O(n)的时间里找到这个节点。找到这个结单后,设要删除的节点为x,合并x的左右子树,然后自底向上维护距离值,不满足左偏性质则交换左右儿子,直至距离值无需更新时,算法结束。

具体而言,设q是x的父节点。合并左右儿子后的新树设为p,即p=merge(left(x),right(x)),设节点x的父节点为q,具体分析如下:

(1)x是原树中的根节点,则删除x节点后,合并左右子树后,算法结束

(2)x不是原树中的根节点,合并后的树为p,新的距离为dis(p),dist(p)与dist(q)的值相比有如下几种

           (a) dist(p)=dist(q)-1。即新树中q的左右儿子距离相等,无法新树p是q的左子树还是右子树,满足左偏条件,不需进行交换

           (b)dist(p)<dist(q)-1。如此时p是q的右子树,则需要更新q的距离值,并一直更新到距离值无需再更新时为止,期间不满足左偏条件的要进行交换;如果p是q的左子树,则需要交换p和right(q),并更新q的距离   值,并一直更新到距离值无需再更新时为止,期间不满足左偏条件的要进行交换

           (c)dist(p)>dist(q)-1。新树的p的距离大于原x节点的距离。如果新树p在q的左子树上,则满足左偏条件,不需要交换和更新,如果新树p在q的右子树上,如果新树p的距离大于q的左子树的距离,则需要交换,同时更新其q的距离,并一直更新到距离值无需再更新时为止,期间不满足左偏条件的要进行交换

 删除任意节点的伪代码如下:

def deleteany(x)

{

q=parent(x)

p=merge(left(x),right(x))

parent(p)=q;

if(q!=NULL&&rson(q)==x)

right(q)=p

if(q!=NULL&&lson(q)==x)

left(q)=p

while(q!=NULL){

if(dist(left(q)>right(q))

swap(left(q),right(q))

if(dist(q)==dist(right(q))+1)

break;

dist(q)=dist(q)+1

q=parent(q)
}

}

上述删除任意节点的复杂度分析如下。不需要向上递推的情况有两种,一是删除的是原树的根节点,即q==NULL,而合并的复杂为不超过O(log(n)),二是删除后dist(p)=dist(q)-1。而需要向上递推的情况也有两种:

(1)dist(p)<dist(q)-1。即删除后新树的p的距离小于right(q)的距离,则需要向上递推更新,而根据扩展性质4,其向上递推的层次为O(log(n))

(2)dist(p)>dist(q)-1。删除后新树的p的距离大于right(q)的距离,且新树p位于q的右子树上,也需要向上递推更新,根据扩展性质4,向上递推不会超过O(log(n))

由上述分析,合并的复杂为不超过O(log(n)),向上递推的复杂为不超过O(log(n)),所以删除任意节点的复杂度不超过O(log(n))

1.3.5 整个堆加/减一个值,或乘上一个整数

在整个堆上加上一个值,首先在根上打上标记,然后删除根、合并左右子树时,将标记下传即可。上述合并的算法伪代码修改为

def merge(A,B)
{
if (A==NULL) return B
if (B==NULL) return A
if (key(A)>key(B))
   swap(A,B)

pushdown(A)                                    //下传标记
right(A)=merge(right(A),B)
if (dist(left(A))<dist(right(A)))           //不满足左偏性质,进行交换
    swap(left(A),right(A))

if(righr(A)==NULL) dist(A)=0
dist(A)=dist(right(A))+1
return A

}

标记函数pushdown里,可以做加法,也可以做减法,或者可以乘上一个正数。

2 总结

左偏数是同时具有左偏性和堆性质的二叉树。左偏树的两种极端情况,一种是有序表,此时的左偏树是一个向左的链,且树的距离为0,深度=n;另外一种是平衡二叉树(完全二叉树是平衡树)。

上述操作的复杂度见下表:

左偏树操作合并插入节点构建操作删除任意节点
复杂度O(log(n+m))O(log(n))O(n)O(log(n))

 

 

 

参考资料:

1 左偏树的特点及其应用,黄源河 .https://wenku.baidu.com/view/20e9ff18964bcf84b9d57ba1.html

2OiWII 左偏树 http://oi-wiki.com/ds/leftist-tree/

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值