回味左偏树(模板)

      之所以叫回味,那是因为在高中的时候偶然也接触了这个数据结构。那时候好像是在学别的东西,然后我先学完了,于是就多学了这个。但是由于种种原因,可能是NOIP没考吧,然后就忘记的差不多了,但是隐约记得一些。前几天也是无意之中看到可并堆,正好也没有它的模板,于是就来“回味”了一番。

   言归正传,左偏树,顾名思义,就是往左偏的一种树,具体表现就是对于任何一个节点,它的左儿子的dist>右儿子的dist,这的的dist是指该点到达往下叶子节点的距离。可以想象,这样的一棵树是高度不平衡的,这也决定了它不能够像平衡树那样快速的查找,说到底它就是一个合并的堆,所以也被称之为可并堆。其实可并堆还有斜堆等,但是左偏树的左偏性质使得它可以更加高效地完成合并操作,下面具体介绍如何做到的。

        首先,这个数据结构的实现要依赖于并查集,因为对于每次合并的操作,我们都要改变两个节点的根,那么我们正好可以利用并查集来储存每个节点根的信息。在这里,我们特别的规定:对于合并操作merge(u,v),u和v并不一定是两棵树的根或者说堆顶,但是我们实际合并的却是两棵树的根。一开始时,每个节点独立为子树,然后再逐渐合并。

        下面具体说说merge操作。merge定义一个返回值,因为合并两棵树一定会有一个新的根节点,我们就返回这个根节点。合并时,先比较两个根的大小,(以大根堆为例)让较大的树的右子树与小的树继续合并,因为右子树的dist更小,所以可以最大程度的减少递归调用次数,这就是左偏树合并效率高的原因。合并后,再维护左偏的性质,如果右儿子的dist已经左儿子的dist,那么交换左右儿子,同时更新父亲的dist。最后别忘了用并查集维护涉及到的点的新根。具体见代码:

inline int merge(int x,int y)
{
	if (!x||!y) return x+y;
	if (tree[x].num<tree[y].num) swap(x,y);
	tree[x].r=merge(y,tree[x].r);				//合并右儿子和相对小的根
	f[tree[x].r]=x;						//维护根节点
	if (tree[tree[x].l].dist<tree[tree[x].r].dist) swap(tree[x].l,tree[x].r);	//维护左偏性质
	if (tree[x].r) tree[x].dist=tree[tree[x].r].dist+1;	//维护dist
	          else tree[x].dist=0;
	return x;						//返回根
}


        然后再说说del(x)操作,这个就更简单了,只需要把该节点的两个子树合并修改根即可。代码也很简洁:

inline int del(int x)
{
	int l=tree[x].l,r=tree[x].r;
	tree[x].l=tree[x].r=tree[x].dist=0;	//清空节点信息
        f[l]=l; f[r]=r;	
	return merge(l,r);			//合并左右子树
}

        如你所见,左偏树(可并堆)是一种编写简单、速度快、易于理解的一种数据结构,与同类型的二叉堆、Fibonacci堆、斜堆比起来具有综合性的优势。Fibonacci堆虽然快但是编程复杂度极高,所以综合来说这是个非常好的数据结构。下面附一个对比的表格,顺便把时间复杂度交代一下:

项目

二叉堆

左偏树

二项堆

Fibonacci堆

构建

O(n)

O(n)

O(n)

O(n)

插入

O(logn)

O(logn)

O(logn)

O(1)

取最小节点

O(1)

O(1)

O(logn)

O(1)

删除最小节点

O(logn)

O(logn)

O(logn)

O(logn)

删除任意节点

O(logn)

O(logn)

O(logn)

O(logn)

合并

O(n)

O(logn)

O(logn)

O(1)

空间需求

最小

较小

一般

较大

编程复杂度

最低

较低

较高

很高


  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值