前言部分
splay是个什么东西呢?
它就是个平衡树,支持以下操作
这些操作还可以用treap,替罪羊树,红黑树,multiset balabala(好像混进去什么奇怪的东西)
这里就只说一下splay(其他的窝不会)(splay窝也不会)
先来几个变量和一些辅助函数:
root:当前平衡树的根是那个节点
sz:整个平衡树的大小
ch[x][0]:x的左儿子的编号
ch[x][1]:x的右儿子的编号
size[x]:x和它的子树的大小
cnt[x]:编号为x的点的权值出现了几次
par[x]:x的父亲的编号
key[x]:编号为x的点的权值
get(x):查询x是它父亲的左儿子还是右儿子(左儿子返回0,右儿子返回1)
pushup(x):统计x的size和cnt
clear(x):将x的ch,size,cnt,key清0(删除用)
代码:
void pushup(int x) { size[x]=size[ch[x][0]]+size[ch[x][1]]+cnt[x];//左子树+右子树+自身的cnt return ; } void clear(int x) { size[x]=0;ch[x][0]=0;ch[x][1]=0;key[x]=0;cnt[x]=0;par[x]=0; return ;//全部清0就好辣 } bool get(int x) { return ((ch[par[x]][1]==x)?1:0);//如果x是自己父亲的右儿子,就返回1,否则返回0 }
正文开始
一.一些基础操作
说了辣么多,还没有介绍平衡树到底是个什么东西
不过不着急,我们先来看看二叉搜索树
二叉搜索树有一个神奇的性质:所有比当前节点x权值小的节点,都在x的左子树里面,权值比x大的点都在x的右子树里
有了这个神奇的性质,在一般情况下树高就是log级别的,查询的复杂度也就降到了log级别,这很好对不对?
但是duliu出题人总是会种出一些歪七扭八的二叉搜索树,就像下面这样(节点里面的数是权值)
对于第二种毒瘤的二叉搜索树,复杂度就退化成了O(n),这不优美。我们想让它成长,让它学会自己平衡。于是,它成长成了平衡树。
splay如何做到自己平衡?
我们看这个丑陋的树:
转一转(右旋)
我们发现,B原本的右儿子成了A的左儿子,B现在的右儿子是A,其余不变
为什么这样转?
我们把B转成了根,那么B就没有父亲了,但是多了A这个右儿子,需要处理成二叉。同时A失去了左儿子,并且A发现B还有一个原来的右儿子F,F比A小,于是A就让F当自己的左儿子
如果我们把A左旋(然后这个树就更不平衡了ρωρ)
我们发现A没有右儿子了,D的左儿子变成了A
旋转的方式和右旋差不多,这里是让D的左儿子认A当爹,让A认D当爹。
不过可惜D并没有左儿子,于是A转完了也就没有右儿子辣。
我们可以总结出旋转的规律:
现在我们要把x转到根的位置(不管现在x在哪)
我们用k表示x是它父亲的左儿子还是右儿子(0是左儿子,1是右儿子),y是x的儿子
就让y的k方向的儿子变成x的与k相反方向的儿子,y认x为爹,同时让y的父亲在y这个方向上的儿子替换成x
说人话版本:
y=par[x],z=par[y],k=get(x),e=get(y)
ch[y][k]=ch[x][k^1]
par[ch[y][k]]=y
par[y]=x
ch[x][k^1]=y
par[x]=z
ch[z][e]=x
代码版本:
void rotate(int x) { int y=par[x],z=par[y],k=get(x),e=get(y); ch[y][k]=ch[x][k^1];par[ch[y][k]]=y; par[y]=x;ch[x][k^1]=y; par[x]=z; if(z) ch[z][e]=x; pushup(y);pushup(x);//记得pushup回去(统计信息) }
当然只旋转一次是肯定不够的,所以我们再来一个splay函数。
splay(x)就是把x旋转到根(当然也可以再带一个参数,让x旋转到那个参数去)
定义fa,让fa一直等于当前x的父亲,一直旋转x,直到x的父亲是0(x是根节点)
注意:如果x,x的父亲,x的爷爷在同一条线上,就先转x的父亲
void splay(int x) { for(int fa;fa=par[x];rotate(x)) if(par[fa]) rotate((get(x)==get(fa))?fa:x); root=x; }
好像有什么不对的???
貌似这些操作一个都没有实现ρωρ
那就开始讲这些操作好了
二.平衡树支持的操作
1.插入X
如果当前平衡树里面没有元素,就直接sz++,root=sz
如果当前点的权值>x,就到左子树找x,反之则到右子树找,直到找到x,然后更新cnt,size,顺便再splay一下
如果找到最后都没有x,就说明x先前不在平衡树里,就新建一个节点,其权值为x,维护cnt,size
void insert(int x) { if(root==0) { sz++; key[sz]=x; root=sz; cnt[sz]=1;size[sz]=1; par[sz]=ch[sz][0]=ch[sz][1]=0; return;//这里由于只有一个节点,就不需要splay了 } int now=root,fa=0; while(1) { if(key[now]==x) { cnt[now]++; pushup(now);//先更新now,再更新fa pushup(fa); splay(now); //为了以后方便,我们要把当前点splay到根 return ; } fa=now; now=ch[now][key[now]<x];//第二维表示的是如果key[now]<x,就返回1,否则是0 if(now==0)//最终没有找着(不存在的节点默认值是0) { sz++; size[sz]=1;cnt[sz]=1; root=sz; key[sz]=x; ch[fa][key[fa]<x]=sz; par[sz]=fa; key[sz]=x; pushup(fa); splay(sz);return ; } } }