正文
说到伸展树,大家想到的可能是复杂,代码冗长,很容易打错,思路却很好理解。
没错,伸展树的确是这么一个东西,如果你做多了题,学会用它之后,你就会觉得很得心应手,轻松面对许多高级数据结构题。这个当然也是一个锻炼人的耐心的东西,有一份代码调了4天emm。
伸展树是一棵二叉搜索树,就是说,它是一棵单一关键字排序的二叉树。
也就是说,比您小的都在您的左边,比您大的都在您的右边,这样平衡,可以使得我们很快的访问一个区间。
重点1
向上翻飞。
别想太多,就是让您可以往上走的操作,比如说下面这棵树,2想到4的位置怎么办emm
这时候我们就要想出一定的方法:
1.首先,考虑的旋转方式肯定是包含我,我的父亲。
2.其次,如果我是我的父亲的左儿子,那么我的父亲肯定是当我的右儿子(因为比我大)。
就有上面的两种情况。
3.我们同时要考虑他们的儿子的继承,包括爷爷是谁的爸爸也要分清。
其他情况自己可以推一推哦~
明显发现,除了绿绿的点之外,其他点所连接的关系都有发生变化,那么该如何考虑这种状态呢?
下面给出函数通式
void rotate(int x,int w){//w等于1意味着我是我爸的左儿子,否则为我爸的右儿子
int f=s[x].f,ff=s[f].f;//代表我的爸爸和爷爷哦~
s[f].son[1-w]=s[x].son[w];//这里的son[0]是左儿子,son[1]是右儿子,如果我是我爸的右儿子,那么说明我的整棵子树都比他大,所以我的左儿子
if(s[x].son[w]!=0) s[s[x].son[w]].f=f;//我爸的右儿子,否则我的左儿子来当我爸的右儿子,这个关系很明显。
if(s[ff].son[1]==f) s[ff].son[1]=x;//如果我爸比我爷爷大,那么说明我比我爷爷大
else s[ff].son[0]=x;//否则我比我爷爷小
s[x].f=ff;//我认我爷做爸爸
s[x].son[w]=f;//认爸爸做儿子
s[f].f=x;//我爸爸的爸爸是我
update(f);//更新底层
update(x);//更新当前层
}
希望理解一下父亲与我的关系和操作有什么关系emm
不断的上旋即可达到目标。。
void splay(int x,int tar){//使得x做tar的儿子
while(s[x].f!=tar){
int f=s[x].f,ff=s[f].f;
if(ff==tar) {//如果只差一步。
if(s[f].son[0]==x) rotate(x,1);
else rotate(x,0);
}
else{//否则就利用双旋
if(s[ff].son[0]==f){
if(s[f].son[0]==x) {rotate(f,1);rotate(x,1);}
else {rotate(x,0);rotate(x,1);}
}
else{
if(s[f].son[0]==x) {rotate(x,1);rotate(x,0);}
else {rotate(f,0);rotate(x,0);}
}
}
}
if(tar==0) root=x;
}
双旋这个东西很曼妙。
后面的操作就很简单啊。
比如说,以维护关键字来找元素:
int findip(int x){
int now=root;
while(x!=s[now].v){
if(x<s[now].v){
if(s[now].son[0]==0) break;
now=s[now].son[0];
}
else{
if(s[now].son[1]==0) break;
now=s[now].son[1];
}
}
return now;
}
找某个数的前驱:
你可以找到与之相近的点,再找他的前驱(即为左儿子的最右边儿子)
int find_next(int x){
int ip=findip(x);
splay(ip,0);
if(s[ip].v<=x && s[ip].son[1]!=0){
ip=s[ip].son[1];
while(s[ip].son[0]!=0) ip=s[ip].son[0];
}
if(s[ip].v<=x) return 0;
return s[ip].v;
}
找后继也是如此哦~:
int find_next(int x){
int ip=findip(x);
splay(ip,0);
if(s[ip].v<=x && s[ip].son[1]!=0){
ip=s[ip].son[1];
while(s[ip].son[0]!=0) ip=s[ip].son[0];
}
if(s[ip].v<=x) return 0;
return s[ip].v;
}
找某个数的排名,相当于把它旋到根之后的左子树大小+1(比他小的数的后一个)
int find_rank(int x){
int ip=findip(x);splay(ip,0);
return s[s[ip].son[0]].c+1;
}
找排名为k的数,根据左右子树的大小往下找:
int find_num(int x){
int now=root;
while(1){
if(x<=s[s[now].son[0]].c) now=s[now].son[0];
else if(x>s[now].same+s[s[now].son[0]].c){
x-=s[now].same+s[s[now].son[0]].c;
now=s[now].son[1];
}
else break;
}
return s[now].v;
}
删除某个节点,可以分情况讨论:
1.没有左右儿子,直接清空整棵树。
2.只有左儿子,让左儿子当根。
3.只有右儿子,让右儿子当根。
4.左右儿子都有,要找这个数的前驱来代替他,因为在左子树它是最大的,所以将它旋到x的儿子后,直接将右儿子继承给他即可。。
void del(int x){
int ip=findip(x);
splay(ip,0);
if(s[ip].v!=x) return ;
if(s[ip].same>1) s[ip].same--,update(ip);
else if(s[ip].son[0]==0 && s[ip].son[1]==0) root=0,tot=0;
else if(s[ip].son[0]!=0 && s[ip].son[1]==0) {root=s[ip].son[0];s[s[ip].son[0]].f=0;}
else if(s[ip].son[0]==0 && s[ip].son[1]!=0) {root=s[ip].son[1];s[s[ip].son[1]].f=0;}
else{
int p=s[ip].son[0];
while(s[p].son[1]!=0) p=s[p].son[1];
splay(p,ip);
root=p;s[p].f=0;
s[s[ip].son[1]].f=p;
s[p].son[1]=s[ip].son[1];
update(p);
}
}
好像写的不怎么样,有点赶吧,对不起了,有什么问题或者想吐槽博客的,都可以在下面登陆之后,耐心的解说大骂楼主。
再次感谢阅读我的博客,希望你们个个IOI金牌