本蒟蒻第一篇blog,写的不好请见谅!
所谓splay就是将一个节点旋转到顶点的操作,而splay这种数据结构就是为了保证树的平衡而做的
什么是树的平衡呢?
首先我们说的树是二叉搜索树,也就是大小分别是左中右从小到大(当然也可以为了需要变成右中左),并且满足整颗左子树全部小于根节点,右子树全部大于根节点。这种二叉搜索树的好处就在于每一次查询耗费的时间仅为层数,不用把整个数组都遍历一遍,而且从中删除数字也非常容易
但是缺点也是很明显的
所以我们引进了一种二叉搜索树的升级版——平衡树
平衡树是一棵二叉搜索树。它除了具有二叉搜索树的全部特征之外,还具有一个关键性的特征:“平衡”,即任意节点的左右子树高度差不超过1。
这个特性决定了它在面对特殊数据(例如那种专门卡普通二叉搜索树的数据)时,能够非常稳定的解决,只是牺牲了一些时间复杂度常数,但是基本不会被卡掉。
平衡树有非常多的实现方式,包括splay,treap(不是严格的平衡树),替罪羊树,红黑树,sbt(size balanced tree即严格平衡),AVL(后面三种本蒟蒻都不会)等
由于treap旋转丑陋,代码量较大,而且万一随机数被卡,那么得不偿失,所以考场上推荐使用我们的优美的——splay
splay的特点是非常稳定,但是常数巨大,因为不论什么操作每次都要进行splay
一个treap需要包含:val, rank(随机数),l, r
splay中包含:val, son[1] (左右孩子), fa(为了方便把父亲带上)
struct ppp{
int son[2], fa, siz, val, num;
}s[300001];
num是一个小小的优化,比如遇到两个相同的数我们就不分开成两个点存储,而是存成一个点,然后把num标成2
为了能够处理那种链状的结构
所以这里我们引进两种
旋转:
(其实和treap的旋转式一样的)
这里我们称被旋转的节点是被提上去的点(如作图为对5旋转,右图为对8旋转)
旋转可以维持二叉搜索树的性质但是可以使树变得平衡
int get(int x){
return (s[s[x].fa].son[1] == x);}//返回这一点是左儿子还是右儿子
void rotate(int x)
{
int f = s[x].fa, ff = s[f].fa, which = get(x);
s[f].son[which] = s[x].son[which ^ 1];
if(s[f].son[which]) s[s[f].son[which]].fa = f;
s[f].fa = x;
s[x].son[which ^ 1] = f;
s[x].fa = ff;
if(ff)
s[ff].son[s[ff].son[1] == f] = x;
update(f); update(x);
}
treap完成这一操作是基于再加一个关键字rank维护rank的二叉堆性质来实现的
而splay就是通过判断两种情况来执行的
情况1:
一个点p和他的父亲同为一边的儿子(如图同为左儿子)
此时应先rotate(p.fa), 再rotate(p)