标签:next 存在 直接 更新 ast 理解 插入 删掉 splay
Look at this!这就是一棵平衡树。
如果这棵树极端一点,它就会变成这样 ↓
顾名思义,Splay译为 “伸展、旋转”
所以它的原理就是:每次查询都会调整树的结构,使被查询频率高的条目更靠近树根。
那么,如何旋转呢?我们先做一个简单的旋转模拟:
任务:将X旋转到Y的位置,并保持这个东西还是一棵平衡树
思路:
Y < Z
X < Y < C
A < X < B
嗯... ...通过上述关系,我们可以通过人脑画出旋转后的图,问题解决(图如下)
我们检查一下,原来的大小关系为:
A < X < B < Y < C < Z
旋转之后大小关系为:
A < X < B < Y < C < Z
诶,大小关系也没有变。所以之前那棵平衡树就可以通过旋转变成这个样子,并且这个时候还是一棵平衡树
注意!前文说了,这只是一个简单的旋转模拟。在实际中,X和Y的位置不只这些,有以下几种情况:
- 有Y是Z的左儿子 X是Y的左儿子
- 有Y是Z的左儿子 X是Y的右儿子
- 有Y是Z的右儿子 X是Y的左儿子
- 有Y是Z的右儿子 X是Y的右儿子
那么现在,我们就来正式探究Splay的一般情况:
从古至今,解决一般性问题都是从特殊性中解决。所以还是上面那个图,我们可以发现这些规律:
Z | 位置始终不变 |
---|---|
X、Y | 相互交换位置 |
B | 原位置改变了 |
A、C | 与自己爸爸的相对位置不变 |
void rotate(int x) //X是要旋转的节点
{
int y=t[x].ff; //X的父亲
int z=t[y].ff; //X的祖父
int k=t[y].ch[1]==x; //X是Y的哪一个儿子 0是左儿子 1是右儿子
t[z].ch[t[z].ch[1]==y]=x; //Z的原来的Y的位置变为X
t[x].ff=z; //X的父亲变成Z
t[y].ch[k]=t[x].ch[k^1]; //X的与X原来在Y的相对的那个儿子变成Y的儿子
t[t[x].ch[k^1]].ff=y; //更新父节点
t[x].ch[k^1]=y; //X的与X原来相对位置的儿子变成Y
t[y].ff=x; //更新父节点
}
好了,旋转完了。看起来似乎没啥毛病。
真的没问题吗?原图中有一条瓜皮的链: Z->Y->X->B
所以,我们可以发现:对于XYZ的不同情况,有不同的旋转方式!
那么这里我直接把这几种情况总结了起来:
- 第一种,X和Y分别是Y和Z的同一个儿子
- 对于情况一,也就是类似上面给出的图的情况,就要考虑先旋转Y再旋转X
- 第二种,X和Y分别是Y和Z不同的儿子
- 对于情况二,就是对X旋转两次,先旋转到Y再旋转到Z
- 不存在Z节点,也就是Y节点就是Splay的根
- 此时无论怎么样都是对于X向上进行一次旋转
void splay(int x,int goal) //将x旋转为goal的儿子,如果goal是0则旋转到根
{
while(t[x].ff!=goal) //一直旋转到x成为goal的儿子
{
int y=t[x].ff,z=t[y].ff; //父节点祖父节点
if(z!=goal) //如果Y不是根节点,则分为上面两类来旋转
(t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);
//这就是之前对于x和y是哪个儿子的讨论
rotate(x); //无论怎么样最后的一个操作都是旋转x
}
if(goal==0) root=x; //如果goal是0,则将根节点更新为x
}
void find(int x)//查找x的位置,并将其旋转到根节点
{
int u=root;
if(!u)return;//树空
while(t[u].ch[x>t[u].val]&&x!=t[u].val)//当存在儿子并且当前位置的值不等于x
u=t[u].ch[x>t[u].val];//跳转到儿子,查找x的父节点
splay(u,0);//把当前位置旋转到根节点
}
void insert(int x)//插入x
{
int u=root,ff=0;//当前位置u,u的父节点ff
while(u&&t[u].val!=x)//当u存在并且没有移动到当前的值
{
ff=u;//向下u的儿子,父节点变为u
u=t[u].ch[x>t[u].val];//大于当前位置则向右找,否则向左找
}
if(u)//存在这个值的位置
t[u].cnt++;//增加一个数
else//不存在这个数字,要新建一个节点来存放
{
u=++tot;//新节点的位置
if(ff)//如果父节点非根
t[ff].ch[x>t[ff].val]=u;
t[u].ch[0]=t[u].ch[1]=0;//不存在儿子
t[tot].ff=ff;//父节点
t[tot].val=x;//值
t[tot].cnt=1;//数量
t[tot].size=1;//大小
}
splay(u,0);//把当前位置移到根,保证结构的平衡
}
void eraser(int x)//删除x
{
int last=Next(x,0);//查找x的前驱
int next=Next(x,1);//查找x的后继
splay(last,0);splay(next,last); //将前驱旋转到根节点,后继旋转到根节点下面 //很明显,此时后 //继是前驱的右儿子,x是后继的左儿子,并且x是叶子节点
int del=t[next].ch[0];//后继的左儿子
if(t[del].cnt>1)//如果超过一个
{
t[del].cnt--;//直接减少一个
splay(del,0);//旋转
}
else t[next].ch[0]=0;//这个节点直接丢掉(不存在了)
}
inline int Next(int x,int f)//查找x的前驱(0)或者后继(1)
{
find(x); int u=root;//根节点,此时x的父节点(存在的话)就是根节点
if(t[u].val>x && f) return u;//如果当前节点的值大于x并且要查找的是后继
if(t[u].val<x && !f)return u;//如果当前节点的值小于x并且要查找的是前驱
u=t[u].ch[f];//查找后继的话在右儿子上找,前驱在左儿子上找
while(t[u].ch[f^1]) u=t[u].ch[f^1];//要反着跳转,否则会越来越大(越来越小)
return u;//返回位置
}
标签:next 存在 直接 更新 ast 理解 插入 删掉 splay
原文地址:https://www.cnblogs.com/BigYellowDog/p/10262121.html