文章目录
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