【学习笔记】平衡树 -
平衡树
- 平衡树是什么?顾名思义就是左儿子与右儿子相平衡的 (即左儿子的权值小于根节点,右儿子大于根节点的权值)
- 而 也是利用这样的性质来进行旋转的
操作
-
这个操作比较重要,也比较麻烦。我们可以看图理解。
-
首先我们建立一颗平衡树(下图
-
我们设 那个电 , 那个电 , 那个电 。那么 分别是 的父亲。我们现在想要把 旋到 上。那么我们会得到的图:
- 首先 成为 的左儿子,又发现 成了 的右儿子(依然合法,平衡树的性质)。如果 本身有右儿子那么就会成为 的左儿子。于是我们就有了下面的代码:
inline void rotate(int x)
{
int y=t[x].fa;//x的父亲
int z=t[y].fa;//y的父亲
int k=t[y].ch[1]==x;//x是y的右儿子
t[z].ch[t[z].ch[1]==y]=x;//如果z原来y变成x
t[x].fa=z;
t[y].ch[k]=t[x].ch[k^1];//x的与x原来在y的相对的那个儿子变成y的儿子
t[t[x].ch[k^1]].fa=y;
t[x].ch[k^1]=y;
t[y].fa=x;//y的父亲是x
}
操作
- 这个操作就是把一个节点旋到根节点的操作(难道只要简单地旋两次就行了
- 这个操作我们分两种情况讨论
- 分别为 的同一方向上的儿子
- 不为 的同一方向上的儿子
- 那么我们大力发现:对于情况一我们先旋 再旋 ,对于情况二我们直接旋两次 (不理解可以手动滑稽一下
- 于是我们就有了下面的代码:
inline void SPlay(int x,int p)
{
while(t[x].fa!=p)
{
int y=t[x].fa;
int z=t[y].fa;
if(z!=p)
(t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);
//讨论是不是同属一个方向上的儿子,然后讨论是那种旋转方式
rotate(x);
}
if(!p) root=x;//如果旋到的是根那么就更行根
}
操作
- 顾名思义就是寻找那个元素具体的位置
- 我们考虑平衡树的性质:越往左越小,越往右越大。于是我们只要不停地向左(如果要找的数更加小)向右递归找答案就行了。找到后把他 到根节点。有点类似与二分查找操作。
- 复杂度
- 于是我们就有了下面的代码:
inline void Find(int x)
{
int u=root;
if(!u) return;
while(t[u].ch[x>t[u].val]&&x!=t[u].val) //一直找不到向左向右递归
u=t[u].ch[x>t[u].val];
SPlay(u,0);//再旋到根节点
}
操作
- 就是加入一个元素 的操作。
- 其实这个操作和上面那个差不多。就是先找在树上有没有这个节点,如果本身就有我们只要对那个点进行操作。否则新开一个节点来存储信息。最后再把这个点 到根就可以了。注意在向下寻找的过程中始终记录他的父亲是什么。
- 于是我们就有了下面的代码:
inline void insert(int x)
{
int u=root,ff=0;
while(u&&t[u].val!=x)
{
ff=u;//记录父亲
u=t[u].ch[t[u].val<x];//寻找
}
if(u) t[u].cnt++;//如果找到了那么就在原来基础上更新
else
{
u=++tot;//新开一个节点
if(ff) t[ff].ch[x>t[ff].val]=u;
t[tot].ch[1]=t[tot].ch[0]=0;
t[tot].fa=ff;
t[tot].siz=1;
t[tot].cnt=1;
t[tot].val=x;//对那个各个信息节点进行修改
}
SPlay(u,0);//再旋到根
}
操作
- 其实就是寻找某个元素的前驱以及后继
- 我们以前驱为例来介绍一下:先确定前驱比他小,所以在左子树上。然后他的前驱是左子树中最大的值所以一直跳右结点就可以了。
- 后继同理不在赘述
- 于是我们就有了下面的代码:
inline int Next(int x,int f)//前驱0
{
Find(x);//先寻找
int u=root;
if(t[u].val>x&&f) return u;//是不是前驱/后继
if(t[u].val<x&&!f) return u;
u=t[u].ch[f];
while(t[u].ch[f^1]) u=t[u].ch[f^1];//要反过来,因为前缀是在右子树里寻找最大的,后继是在左子树寻找最小的
return u;
}
操作
- 删除操作吗。。。真的不太想说了。
- 我们首先找到这个树的前驱把他旋到根,再找到后继节点旋到根节点下面。于是在右子树比后继小的且比前驱大的有且仅有当前数。然后再看这个节点的数量来处理就行了。如果只有 个直接根除。否则再把那个点(后继的左儿子)旋到根节点。
- 于是我们就有了下面的代码:
inline void Del(int x)
{
int las=Next(x,0);
int nex=Next(x,1);//前驱后继
SPlay(las,0);
SPlay(nex,las);//旋丫旋
int del=t[nex].ch[0];//那个点就是后继的左儿子
if(t[del].cnt>1)
{
t[del].cnt--;
SPlay(del,0);//旋到根节点
}
else t[nex].ch[0]=0;//直接删除
}