前言

今天不容易有一天的自由学习时间,当然要用来“学习”。在此记录一下今天学到的最基础的平衡树。

定义

平衡树是二叉搜索树和堆合并构成的数据结构,它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

这里仅仅说明一下平衡树中的\(Splay\)算法

进入正题

平衡树中有许多种类:红黑树、\(AVL\)树,伸展树,\(Treap\)等等,但是\(Splay\)算法算是可用性很强的一种了。也就是说比较稳定。

在\(Splay\)算法中,一个处处都要用到的东西就是旋转,即将当前节点与其前边一个节点依次旋转到目标位置。由于这个树是一个二叉搜索树,所以旋转之后要保证性质不变。我们就需要找到当前节点的父亲和爷爷节点,然后先更新爷爷节点与当前节点之间的关系,然后将父亲节点与该节点所属关系的另一个子树连起来,最后再处理一下该节点和父亲节点的关系。

我们举个例子(毕竟不太好理解)我们设这三个点分别为\(x,y,z\),从左往右分别是后一个的儿子,我们先把图画一下(\(x\)的子节点我随便用一堆东西表示):



在这里\(x\)是\(y\)的左节点。那么我们下一步就是把\(x\)变为\(z\)的子节点,也就是把\(y\)换下来。这一步很简单,变成了下边这样:

然后就是关键了,因为把\(x\)旋转上来,\(x\)也是有子节点的,所以我们需要进一步处理。首先记录一下\(x\)是\(y\)节点的左还是右儿子,在这个例子里是左儿子,因为我们不能破坏这个树的顺序和性质,所以就需要让\(y\)的右儿子不变且所有满足性质的比\(x\)小的他的左儿子还是\(x\)的左儿子,而\(y\)的左儿子变成\(x\)的右儿子,也就是下边这个图:(刚刚没有\(y\)的右节点,现在加上,编号与之前的不同,自己理解理解\(qwq\)):

类似的一个图,这样就完成了一次旋转。最后不样忘记也是需要\(pushup\)的,来维护点的儿子数量和自己的数量。

下边放一下\(Splay\)和旋转的代码

void rotate(int x){//旋转
int y=t[x].fa;
int z=t[y].fa;
int k=t[y].ch[1]==x;//找到x是y的左还是右节点,便于进行上文中所说操作
t[z].ch[t[z].ch[1]==y]=x;//以下都是上边说过的操作。
t[x].fa=z;
t[y].ch[k]=t[x].ch[k^1];
t[t[x].ch[k^1]].fa=y;
t[x].ch[k^1]=y;
t[y].fa=x;
pushup(y);
pushup(x);
}
void splay(int x,int goal){//旋转
while(t[x].fa!=goal){//父亲节点不是目标
int y=t[x].fa;
int z=t[y].fa;
if(z!=goal){//爷爷节点也不是目标
(t[y].ch[0]==x)^(t[z].ch[0]==x)?rotate(x):rotate(y);//其中一个点的左儿子是x就翻转x,不然翻转y(因为树有序,不能破坏性质)
}//上边这句话还需要细细钻研,本题解以后还会完善
rotate(x);
}
if(goal == 0){//到了根节点的话让根节点更新
root = x;
}
}

一些操作

上边把\(Splay\)的操作说了一遍,也就是关键的旋转应该比较清晰了,下边我们就需要进行一些操作了。

平衡树有许多可以进行的操作:删除,插入,查询一个值\(x\)的排名,查询\(x\)排名的值,还有值\(x\)的前驱和后继。(前驱定义为小于 \(x\),且最大的数,后继定义为大于 \(x\)且最小的数)。这些都需要上边的旋转,所以旋转明白了,这些也就比较容易了。首先是结构体的定义:

struct Node
{
int ch[2];//子节点
int fa;//父节点
int cnt;//数量
int val;//值
int son;//儿子数量
}t[maxn];

\(1\)、插入

我们肯定要从根节点开始向下找到符合这个值的点,或者新建一个点,那么我们首先另\(u\)为根节点,其父亲为\(0\),然后如果有根且插入的值不是当前节点的值,那么我们就需要向子节点扩展,这里我的扩展比较巧妙,因为儿子只有左右分别用\(0\)、\(1\)表示,所以直接用判断来找到底是左还是右,也就是这样的式子:\(x>t[u].val\)。如果大于的话,就是右儿子。否则就是左儿子。所以直接更新到当前节点的儿子节点。

当跳出向下扩展的循环,就说明当前点值就是要插入的值,我们只需要将大小加一就行。如果没有这样的节点,那么就新建一个节点,然后将父亲的儿子节点置为当前节点,而左右儿子的判断如上文所说。其余的东西都是初始化,具体看代码,最后不要忘了再将当前节点\(Splay\)到根(几乎每种操作都需要\(Splay\),查询前驱后继和排名的值不用):

void Add(int x){
int u=root,fa=0;
while(u&&t[u].val!=x){
fa=u;
u=t[u].ch[x>t[u].val];
}
if(u){//有的话直接大小加一
t[u].cnt++;
}
else{//没有该值的节点
u=++tot;
if(fa)t[fa].ch[x>t[fa].val]=u;
t[tot].ch[1]=0;
t[tot].ch[0]=0;
t[tot].val=x;
t[tot].fa=fa;
t[tot].cnt=1;
t[tot].son=1;
}
splay(u,0);
}

\(2\)、查找值为\(x\)的排名:

根据这个判断\(x>t[u].val\)依次找\(x\)的位置,最后\(Splay\)一下就好了:

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);//旋转
}

这个到最后把这个位置\(Splay\)到了根,所以答案就是当前\(Find\)之后根的左儿子的儿子数。

\(3\)、求前驱后继(这个操作求出来的是节点编号)

我们首先需要找到排名,也就是操作\(2\),然后\(Splay\)到根节点,如果值大于当前值且查找的是后继或者小于当前且找前驱就直接返回,否则就向子节点转移。找到转移后最接近当前值的点,也就是说,如果第一次不满足,假如找前驱就向左走一个,然后找到左儿子的右节点的最下边的点,也就是最接近这个查找的值的点。

int Fr_last(int x,int flag){//前驱flag为0,后继为1
Find(x);//找到位置
int u=root;//根开始
if((t[u].val>x&&flag) || (t[u].val<x && !flag)){//当前点满足就直接返回
return u;
}
u=t[u].ch[flag];//向目标点(左或右)转移
while(t[u].ch[flag^1])u=t[u].ch[flag^1];//找到转移后最接近当前值的点
return u;//返回
}

\(4\)、查找排名为\(x\)的值

首先从根节点开始,如果一共都没有\(x\)个数,那么就直接返回\(0\),不然的话就分别记录一下当前点的左右节点,然后判断,如果当前点的子节点树加上当前点的值的数量小于查找的排名,直接减去然后走到右儿子,不然就走到左儿子就行了。

int Find_thval(int x){
int u=root;//根开始
if(t[u].son<x){//如果没有这么多,直接返回0
return 0;
}
while(666666){//一直循环
int y=t[u].ch[0];//记录左儿子
if(x>t[y].son+t[u].cnt){//排名大就减去,然后走到右节点
x-=t[y].son+t[u].cnt;
u=t[u].ch[1];
}
else{
if(x<=t[y].son){//否则走到左节点
u=y;
}
else return t[u].val;//如果排名比上边的小,且比左节点的值大,这就是满足的价值,直接返回
}
}
}

我的这个代码有一些等号的取舍不同,所以在查找的时候传递参数需要加上一。

\(5\)、删除

我们需要首先找出这个点的前驱和后继,然后旋转下去,要删除的就是后继的左儿子,假如这个点的数量大于\(1\),就直接数量减一就好了,然后翻转到根节点,如果小于等于\(1\),那么就把这个点变成\(0\),结束!

void Delete(int x){
int Front=Fr_last(x,0);//前驱
int Last=Fr_last(x,1);//后继
splay(Front,0);//旋转
splay(Last,Front);
int del=t[Last].ch[0];//找到需要删除的点
if(t[del].cnt>1){//大于1直接减
t[del].cnt--;
splay(del,0);
}
else{//否则直接删除
t[Last].ch[0]=0;
}
}

总结

以上就是\(Splay\)的一些实现和操作,以后博客还会进行修改和完善,这些只是暂时自学时的理解,如果有神犇能给蒟蒻一些指导那就更好了。

完结撒花\(qwqq\)。

下边推荐一个板子题普通平衡树板子

板子题代码:

细节的注释上边都写过了,祝愿大家学习愉快\(qwq\)。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5+10;
const int Inf = 2147483647;
struct Node{
int son,ch[2],fa,cnt,val;
}t[maxn];
int n,tot,root;
void pushup(int x){
t[x].son = t[t[x].ch[0]].son+t[t[x].ch[1]].son+t[x].cnt;
}
void rotate(int x){
int y=t[x].fa;
int z=t[y].fa;
int k=t[y].ch[1]==x;
t[z].ch[t[z].ch[1]==y]=x;
t[x].fa=z;
t[y].ch[k]=t[x].ch[k^1];
t[t[x].ch[k^1]].fa=y;
t[x].ch[k^1]=y;
t[y].fa=x;
pushup(y);
pushup(x);
}
void splay(int x,int goal){
while(t[x].fa!=goal){
int y=t[x].fa;
int z=t[y].fa;
if(z!=goal){
(t[y].ch[0]==x)^(t[z].ch[0]==x)?rotate(x):rotate(y);
}
rotate(x);
}
if(goal == 0){
root = x;
}
}
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);
} void Add(int x){
int u=root,fa=0;
while(u&&t[u].val!=x){
fa=u;
u=t[u].ch[x>t[u].val];
}
if(u){
t[u].cnt++;
}
else{
u=++tot;
if(fa)t[fa].ch[x>t[fa].val]=u;
t[tot].ch[1]=0;
t[tot].ch[0]=0;
t[tot].val=x;
t[tot].fa=fa;
t[tot].cnt=1;
t[tot].son=1;
}
splay(u,0);
}
int Fr_last(int x,int flag){
Find(x);
int u=root;
if((t[u].val>x&&flag) || (t[u].val<x && !flag)){
return u;
}
u=t[u].ch[flag];
while(t[u].ch[flag^1])u=t[u].ch[flag^1];
return u;
}
void Delete(int x){
int Front=Fr_last(x,0);
int Last=Fr_last(x,1);
splay(Front,0);
splay(Last,Front);
int del=t[Last].ch[0];
if(t[del].cnt>1){
t[del].cnt--;
splay(del,0);
}
else{
t[Last].ch[0]=0;
}
}
int Find_thval(int x){
int u=root;
if(t[u].son<x){
return 0;
}
while(666666){
int y=t[u].ch[0];
if(x>t[y].son+t[u].cnt){
x-=t[y].son+t[u].cnt;
u=t[u].ch[1];
}
else{
if(x<=t[y].son){
u=y;
}
else return t[u].val;
}
}
}
int main(){
int n;
Add(Inf);
Add(-Inf);
scanf("%d",&n);
while(n--){
int opt;
scanf("%d",&opt);
int x;
if(opt == 1){
scanf("%d",&x);
Add(x);
}
if(opt == 2){
scanf("%d",&x);
Delete(x);
}
if(opt == 3){
scanf("%d",&x);
Find(x);
int ans = t[t[root].ch[0]].son;
printf("%d\n",ans);
}
if(opt == 4){
int ans;
scanf("%d",&x);
ans = Find_thval(x+1);
printf("%d\n",ans);
}
if(opt == 5){
scanf("%d",&x);
int ans = Fr_last(x,0);
printf("%d\n",t[ans].val);
}
if(opt == 6){
scanf("%d",&x);
int ans = Fr_last(x,1);
printf("%d\n",t[ans].val);
}
}
return 0;
}

普通平衡树学习笔记之Splay算法的更多相关文章

  1. BST,Splay平衡树学习笔记

    BST,Splay平衡树学习笔记 1.二叉查找树BST BST是一种二叉树形结构,其特点就在于:每一个非叶子结点的值都大于他的左子树中的任意一个值,并都小于他的右子树中的任意一个值. 2.BST的用处 ...

  2. 平衡树学习笔记(3)-------Splay

    Splay 上一篇:平衡树学习笔记(2)-------Treap Splay是一个实用而且灵活性很强的平衡树 效率上也比较客观,但是一定要一次性写对 debug可能不是那么容易 Splay作为平衡树, ...

  3. 平衡树学习笔记(6)-------RBT

    RBT 上一篇:平衡树学习笔记(5)-------SBT RBT是...是一棵恐怖的树 有多恐怖? 平衡树中最快的♂ 不到200ms的优势,连权值线段树都无法匹敌 但是,通过大量百度,发现RBT的代码 ...

  4. 平衡树学习笔记(5)-------SBT

    SBT 上一篇:平衡树学习笔记(4)-------替罪羊树 所谓SBT,就是Size Balanced Tree 它的速度很快,完全碾爆Treap,Splay等平衡树,而且代码简洁易懂 尤其是插入节点 ...

  5. 平衡树学习笔记(2)-------Treap

    Treap 上一篇:平衡树学习笔记(1)-------简介 Treap是一个玄学的平衡树 为什么说它玄学呢? 还记得上一节说过每个平衡树都有自己的平衡方式吗? 没错,它平衡的方式是......rand ...

  6. GMM高斯混合模型学习笔记(EM算法求解)

    提出混合模型主要是为了能更好地近似一些较复杂的样本分布,通过不断添加component个数,能够随意地逼近不论什么连续的概率分布.所以我们觉得不论什么样本分布都能够用混合模型来建模.由于高斯函数具有一 ...

  7. 【学习笔记】splay入门(更新中)

    声明:本博客所有随笔都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。 前言 终于学习了 spaly \(splay\) !听说了很久,因为dalao总 ...

  8. 强化学习-学习笔记7 | Sarsa算法原理与推导

    Sarsa算法 是 TD算法的一种,之前没有严谨推导过 TD 算法,这一篇就来从数学的角度推导一下 Sarsa 算法.注意,这部分属于 TD算法的延申. 7. Sarsa算法 7.1 推导 TD ta ...

  9. 【学习笔记】 Adaboost算法

    前言 之前的学习中也有好几次尝试过学习该算法,但是都无功而返,不仅仅是因为该算法各大博主.大牛的描述都比较晦涩难懂,同时我自己学习过程中也心浮气躁,不能专心. 现如今决定一口气肝到底,这样我明天就可以 ...

随机推荐

  1. 开发小白可以一年涨薪10w?这份java文档功不可没,学透你也可以

    靠这份文档,跳槽涨薪10K 金九银十的时候我分享了一份面试文档给我的兄弟,没想到这哥们2个月之内斩获数个BAT的offer, 最后选择了一个他最想要去的公司,既然有这么好的效果,我就打算把这份文档分享 ...

  2. 带你学够浪:Go语言基础系列 - 8分钟学复合类型

    ★ 文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) " 对于一般的语言使用者来说 ,20% ...

  3. 如何 SSH 到 Linux 服务器里的特定目录及执行命令?

    你是不是有遇到过这样的场景?使用 SSH 命令进入到服务器,然后再用 cd 命令进入到对应目录,再继续进行你的工作. 这种操作对于新手来讲特别常见,良许之前也是这样.在本文,老司机将带你来进行更高效的 ...

  4. vc6.0打开类向导时报错-Parsing error: Expected ";".Input Line: "解决方法

    --------------------------- Microsoft Visual C++ --------------------------- Parsing error:  Expecte ...

  5. Domain Adaptive Faster R-CNN:经典域自适应目标检测算法,解决现实中痛点,代码开源 | CVPR2018

    论文从理论的角度出发,对目标检测的域自适应问题进行了深入的研究,基于H-divergence的对抗训练提出了DA Faster R-CNN,从图片级和实例级两种角度进行域对齐,并且加入一致性正则化来学 ...

  6. 使用IntelliJ/Eclipse生成类图

    IntelliJ可以安装一个免费的pugins - Code Iris. PlantUML 在Eclipse中 - ObjectAidPapyrusEclipse Modeling Tools 查看原 ...

  7. Spring AOP学习笔记04:AOP核心实现之创建代理

    上文中,我们分析了对所有增强器的获取以及获取匹配的增强器,在本文中我们就来分析一下Spring AOP中另一部分核心逻辑--代理的创建.这部分逻辑的入口是在wrapIfNecessary()方法中紧接 ...

  8. 深度学习“四大名著”发布!Python、TensorFlow、机器学习、深度学习四件套!

    Python 程序员深度学习的"四大名著": 这四本书着实很不错!我们都知道现在机器学习.深度学习的资料太多了,面对海量资源,往往陷入到"无从下手"的困惑出境. ...

  9. Linux安装docker笔记

    更新yum操作 yum -y update 安装docker yum install -y docker 或者yum install -y docker-engine 启动docker  servic ...

  10. python+opencv切割细胞及细胞团(持续更新)

    内容包括:游离细胞的切割,有效细胞的信息提取,找出非正常细胞,细胞团的切割,找出非正常细胞团 代码较多,请移步到我的github