平衡树·splay

1.About splay

s p l a y splay splay是实现平衡树的一种方法
他需要满足一个条件,就是对于一个点为根节点的子树,他的左子树全部小于根节点,右子树全部大于根节点
那他为什么叫这个名字呢?
我们查询一下百度翻译
在这里插入图片描述
但是事实上,感觉 s p l a y splay splay跟张开没什么关系…

可以说成展开树吧

s p l a y splay splay的基本功能跟STL里面的 m u l t i s e t multiset multiset s e t set set)比较像

他的思想的优秀之处在于,他每次都把操作完\要操作的点旋转到根节点并且保证满足他的性质,这样可以便于操作

2.基本操作

2.1 数组是干啥的?

s p l a y splay splay中我们要用到一些数组
fa[x]表示x的爸爸是谁
son[x][2]表示x的儿子分别是谁,son[x][0]表示他的左儿子,son[x][1]表示他的右儿子,把他的儿子们存到一个数组里是为了旋转的时候比较方便,我们后面说
siz[x]表示以x为根节点的子树的大小
key[x]表示x点上存的值
recy[x]表示值为key[x]的数出现了几次(我们把它存在一个点上)
tot表示这颗 s p l a y splay splay里面有几个不同的权值
rt表示根节点的位置

2.2 基本操作

clear操作
这个操作和下面要说的删除函数erase连用,用于彻底清除一个点的所有权值(因为这个点后面继续往里加值得时候可能还会用到)

其实非常的简单,把所有数组都设成0就可以了

inline void clear(int x){
   
	fa[x]=son[x][0]=son[x][1]=siz[x]=key[x]=recy[x]=0;
}

locate操作
这个操作和下面的 s p l a y splay splay的精华splay函数和rotate函数连用,用于判断一个点是他爸爸的左儿子还是右儿子

判断方法很简单,就是判断他爸爸的右儿子是不是他自己就可以了

inline bool locate(int x){
   
	return son[fa[x]][1]==x;	
}

update操作
当我们进行旋转或者更新之后,原来的点之间的数量关系就有可能发生变化,这时候我们需要进行更新,让每个节点对应的siz值是对的

inline void update(int x){
   
	if(x){
   
		siz[x]=recy[x];
		if(son[x][0])siz[x]+=siz[son[x][0]];
		if(son[x][1])siz[x]+=siz[son[x][1]];
	}
}

注意if(x)不能省

3.splay

3.1 rotate函数

rotate(旋转),用来将一个点向我们想要的地方(向上)移一层

rotate分为左旋和右旋,下面以右旋为例

在这里插入图片描述
手画的好丑啊
我们想把X节点转到F的位置,我们不能单纯的把X转上去,因为这样会让X的度数变成4(G,L,R,F),不满足二叉树的性质了,所以我们必须要想一个办法。

我们发现,因为在左图中,R位于X的右子树,所以R应该比X大,所以当旋转完了之后,为了保证性质,所以R应该也在X的右子树,但是右子树已经被X曾经的爸爸给占了。但F的左子树不是还空着吗,就把R接到那里好了。

我们看左图,条件有L<X<R<F<B,在右图中,这个条件还是成立的!!!
也就是说,如果我们这样旋转,splay的性质并没有改变,所以左图和右图其实是等价的

所以这样旋转是合法的

当然左旋也差不多(可以想成F点从右图转到左图的过程)

当然,不要忘了update啊,X,F的关系都变了(特别是F原来是X的爸爸现在变成他儿子了),所以我们要update一下。

=========================
思想差不多就是那样,现在来简单说一下怎么把X旋上去

大家应该学过链表吧,我们在链表里想插入一个东西还挺麻烦的

这里的操作方法跟链表插入差不多,但是不同之处是所有点我们都知道他的编号,所以我们可以都存下来,然后就根本不用考虑顺序,瞎连就可以了

在这里插入图片描述
在这里插入图片描述

其中红线表示起点认终点为儿子,黑线表示终点认起点是爸爸

旋转完了之后的结果就是X之前的爸爸变成了自己的右儿子,自己之前都右儿子变成了自己的孙子,自己的爷爷变成了自己的爸爸,自己之前都兄弟变成了自己的孙子

inline void rotate(int x){
   
	int faz=fa[x],grand=fa[faz],side=locate(x);
	son[faz][side]=son[x][side^1],fa[son[faz][side]]=faz;//左右旋同时考虑
	son[x][side^1]=faz,fa[faz]=x;
	fa[x]=grand;
	if(grand)son[grand][son[grand][1]==faz]=x;//只有他有爷爷的时候才需要更新grand的儿子的情况
	update(faz),update(x);	//因为旋转后原来的faz跑到x下面了,所以要先更新faz
}

3.2 splay函数

我们学会了rotate,但是rotate只能向上转一层,我们要让他转到根节点,那怎么办呢?

我们用一个 s p l a y splay splay函数多次调用rotate来达到这一目的

但是我们需要分类讨论:
在这里插入图片描述
(声明洛谷版权)

为什么需要分类讨论呢?
我们看下面这张图可以更好的理解
在这里插入图片描述
在这里插入图片描述
我们发现,如果我们对于x,y,都是左(右)儿子的情况,如果我们连转两次x的话,那么我们发现X-Y-Z-B的链是始终存在的,这样的话就容易被卡掉

inline void splay(int x){
   
	for(int faz;faz=fa[x];rotate(x))
		if(fa[faz])
			rotate(locate(x)==locate(faz)?faz
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值