本蒟蒻第一篇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)
情况2:
一个点p和他的父亲不是同一边的儿子
此时两次rotate(p)即可
至于为什么要这样做才能最快……可以去看看Dr.Tarjan的论文
一直这样做直到要求的点被旋转到根节点
于是就有了史上最好看的splay代码
void splay(int x)
{
for(int f; (f = s[x].fa); rotate(x))
if(s[f].fa)
rotate((get(f) == get(x)) ? f : x);
root = x;
}
插入操作
其实很简单,就是一路寻找,如果小于那么就找左子树,否则找右子树,等于就直接num++
pre操作(即寻找一个点的前继)
这个很n简单,把该点rsplay到根节点,然后这棵树的左子树的一直取右节点便是前继了
同理可以做next操作(即求一个点的后继)
删除操作:
把一个点splay到根节点,然后直接移除,运用pre找到前继后直接移到根节点
最后附上完整的代码:
1829. [Tyvj 1728]普通平衡树
【题目描述】
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)
【输入格式】
第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)
【输出格式】
对于操作3,4,5,6每行输出一个数,表示对应答案
【样例输入】
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
【样例输出】
84185
492737
#include<cstdio>
#include<cstring>
#include<iostream>
#undef main
using namespace std;
inline int read()
{
int x = 0, f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c < '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int n, m, cnt, root;
struct ppp{
int son[2], fa, siz, val, num;
}s[300001];
void clear(int x){s[x].son[0] = s[x].son[1] = s[x].fa = s[x].siz = s[x].val = s[x].num = 0;}
void update(int x){s[x].siz = s[s[x].son[0]].siz + s[s[x].son[1]].siz + s[x].num;}
int get(int x){return (s[s[x].fa].son[1] == x);}//返回这一点是左儿子还是右儿子
//这里为了方便splay把zigzag打在一起了
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);
}
//史上最优美的splay
void splay(int x)
{
for(int f; (f = s[x].fa); rotate(x))
if(s[f].fa)
rotate((get(f) == get(x)) ? f : x);
root = x;
}
void insert(int x, int pos)
{
if(x == s[pos].val)
{
s[pos].num++;
splay(pos);
return ;
}
if(x < s[pos].val)
{
if(!s[pos].son[0])
{
clear(++cnt);
s[cnt].fa = pos, s[cnt].val = x, s[cnt].siz = 1, s[cnt].num = 1;
if(pos) s[pos].son[0] = cnt;
splay(cnt);
}
else
insert(x, s[pos].son[0]);
}
else
{
if(!s[pos].son[1])
{
clear(++cnt);
s[cnt].fa = pos, s[cnt].val = x, s[cnt].siz = 1, s[cnt].num = 1;
if(pos) s[pos].son[1] = cnt;
splay(cnt);
}
else
insert(x, s[pos].son[1]);
}
}
int getrank(int x, int pos)
{
if(s[pos].val == x)
{
splay(pos);
return s[s[pos].son[0]].siz + 1;
}
if(s[pos].val > x) return getrank(x, s[pos].son[0]);
else return getrank(x, s[pos].son[1]);
}
int getval(int x, int pos)
{
if(x > s[s[pos].son[0]].siz && x <= s[s[pos].son[0]].siz + s[pos].num)
{
splay(pos);
return s[pos].val;
}
if(x <= s[s[pos].son[0]].siz) return getval(x, s[pos].son[0]);
else return getval(x - s[s[pos].son[0]].siz - s[pos].num, s[pos].son[1]);
}
int pre()
{
int pos = s[root].son[0];
while(s[pos].son[1]) pos = s[pos].son[1];
return pos;
}
int nxt()
{
int pos = s[root].son[1];
while(s[pos].son[0]) pos = s[pos].son[0];
return pos;
}
void del(int x)
{
int kth = getrank(x, root);
if(s[root].num > 1){s[root].num --; return;}
if(!s[root].son[0] && !s[root].son[1]){clear(root);return;}
if(!s[root].son[0])
{
root = s[root].son[1];
clear(s[root].fa);
s[root].fa = 0;
return ;
}
if(!s[root].son[1])
{
root = s[root].son[0];
clear(s[root].fa);
s[root].fa = 0;
return ;
}
int gen = root, left = pre();
splay(left);
s[root].son[1] = s[gen].son[1];
s[s[gen].son[1]].fa = root;
clear(gen), update(gen);
}
/*void dfs(int pos)
{
if(!pos) return;
dfs(s[pos].son[0]);
cout<<s[pos].val<<" ";
dfs(s[pos].son[1]);
}*/
int main()
{
int tmp, tmmp;
scanf("%d", &n);
while(n --)
{
scanf("%d%d", &tmp, &tmmp);
if(tmp == 1) insert(tmmp, root);
if(tmp == 2) del(tmmp);
if(tmp == 3) insert(tmmp, root), printf("%d\n", getrank(tmmp, root)), del(tmmp);
if(tmp == 4) printf("%d\n", getval(tmmp, root));
if(tmp == 5) insert(tmmp, root), printf("%d\n", s[pre()].val),del(tmmp);
if(tmp == 6) insert(tmmp, root), printf("%d\n", s[nxt()].val),del(tmmp);
//dfs(root), cout<<endl;
}
return 0;
}