首先感谢litble的精彩讲解,原文博客:

litble的小天地

在学完二叉平衡树后,发现这是只是一个不稳定的垃圾玩意,真正实用的应有Treap、AVL、Splay这样的查找树。于是最近刚学了学了点Splay。

一般地【一般地】,Splay有一下操作:

  1. insert    插入
  2. find       查找
  3. del        删除
  4. pre        查找前驱
  5. post      查找后缀
  6. Splay    *伸展

其中前几个都是普通二叉查找树就有的操作,Splay操作则是Splay tree的特有的。

我们先建一个结构体:

class node
{
public:
int v,ch[2],f;
}e[maxn];

储存每个节点的信息:权值,左右儿子,父亲,【还可以有树的大小等,因需而异】

splay

为了方便,我们先讲splay操作

splay ,顾名思义,就是伸展,将某个节点在保持树的性质的前提下伸展到根节点

有什么用呢?

想象世界上有那么多博客,大神们的博客每天都有成千上万人访问,而我这种蒟蒻一般就没什么人看,所以每次将访问的节点伸展到根,访问少的自然就在下面,访问多的就在上边,大大减少的查询时间。

虽说某一次的查询复杂度可能达到O(n),但平摊下平均是logn的,具体我不会证= =

所以splay分3种情况伸展【其实有6种】:

1、当前节点u的父亲为根节点,这个时候我们只需一次翻转就可以到达根结点了

2、当前节点u与其父节点都为同一侧的儿子,即zig-zig型或zag-zag型,这个时候我们需要将父节点翻转一次,再将u翻转一次

3、当前节点u与其父节点为异侧节点,即zig-zag或zag-zig型,这个时候我们需将u翻转两次

代码如下【借鉴litble的,写的确实很巧妙,合并了zig-zag操作,巧妙利用C++真为1假为0与数组的0和1下标结合】

#define isr(u) (e[e[u].f].ch[1]==u)

.........

inline void spin(int u)
{
int s=isr(u),fa=e[u].f;
e[u].f=e[fa].f;
e[e[fa].f].ch[isr(fa)]=u;
e[fa].f=u;
e[fa].ch[s]=e[u].ch[s^1];
if(e[u].ch[s^1]) e[e[u].ch[s^1]].f=fa;
e[u].ch[s^1]=fa;
} inline void splay(int u)
{
while(e[u].f)
{
if(!e[e[u].f].f) spin(u);
else if(isr(u)^isr(e[u].f)) spin(u),spin(u);
else spin(e[u].f),spin(u);
}
root=u;
}

insert

inline void insert(int& u,int v,int fa)
{
if(!u) e[u=++siz].v=v,e[u].f=fa,e[u].ch[0]=e[u].ch[1]=0,splay(u),cnt++;
else if(v<e[u].v) insert(e[u].ch[0],v,u);
else insert(e[u].ch[1],v,u);
}

插入操作与二叉查找树相同,不过多了一个splay操作

find

int find(int v,int u=root)
{
if(!u) return 0;
if(e[u].v==v) {splay(u);return u;}
if(e[u].v>v) return find(v,e[u].ch[0]);
else return find(v,e[u].ch[1]);
}

按照查找树左小右大的性质查找,记得每次访问时先splay一下

del

void del(int u)
{
if(!u) return;
splay(u);
if(e[u].ch[0]*e[u].ch[1]==0) root=e[u].ch[0]+e[u].ch[1];
else
{
int t=e[u].ch[1];
while(e[t].ch[0]) t=e[t].ch[0];
e[t].ch[0]=e[u].ch[0];e[e[u].ch[0]].f=t;
root=e[u].ch[1];
}
e[root].f=0;
cnt--;
}

先splay到根,这下就很好讨论了,不像二叉查找树那么麻烦,就分两种情况:

1、u有一次儿子为空,那么直接删去就好了,将另一侧节点设为根节点

2、u两子健全,先找到u的后继t【前驱类似】,将根节点的左儿子拔下来,插到后继t的左儿子上,这时候u只剩右儿子无牵无挂,直接将root交给右儿子随风而去~~

pre和post

int pre(int v,int u=root)
{
if(!u) return 0;
if(e[u].v==v) {splay(u);return u;}
if(e[u].v>v) return pre(v,e[u].ch[0]);
else
{
int x=pre(v,e[u].ch[1]);
if(!x) return u;
return e[u].v>e[x].v ? u:x;
}
}
int post(int v,int u=root)
{
if(!u) return 0;
if(e[u].v==v) {splay(u);return e[u].v;}
if(e[u].v<v) return post(v,e[u].ch[1]);
else
{
int x=post(v,e[u].ch[0]);
if(!x) return u;
return e[u].v<e[x].v ? u:x;
}
}

和插入类似,利用查找树的性质查找,注意当找到一个符合条件的节点时不一定是最优的,以后继为例,还要向左儿子找有没有更小但不比v小的节点

【我这里返回的是节点编号,直接返回值也可以而且更加方便】

大概基本的操作就这些啦,我们可以做做题练习一下:

洛谷P2286 宠物收养所

比较裸的一道练习,带前驱与后继操作

#include<iostream>
#include<cstdio>
#include<algorithm>
#define isr(u) (e[e[u].f].ch[1]==u)
using namespace std;
const int maxn=80005,INF=2000000000,P=1000000; inline int read()
{
int out=0,flag=1;char c=getchar();
while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();}
return out*flag;
} class node
{
public:
int v,ch[2],f;
}e[maxn];
int siz=0,root=0,cnt=0,flag=-1; inline void spin(int u)
{
int s=isr(u),fa=e[u].f;
e[u].f=e[fa].f;
e[e[fa].f].ch[isr(fa)]=u;
e[fa].f=u;
e[fa].ch[s]=e[u].ch[s^1];
if(e[u].ch[s^1]) e[e[u].ch[s^1]].f=fa;
e[u].ch[s^1]=fa;
} inline void splay(int u)
{
while(e[u].f)
{
if(!e[e[u].f].f) spin(u);
else if(isr(u)^isr(e[u].f)) spin(u),spin(u);
else spin(e[u].f),spin(u);
}
root=u;
} inline void insert(int& u,int v,int fa)
{
if(!u) e[u=++siz].v=v,e[u].f=fa,e[u].ch[0]=e[u].ch[1]=0,splay(u),cnt++;
else if(v<e[u].v) insert(e[u].ch[0],v,u);
else insert(e[u].ch[1],v,u);
} int find(int v,int u=root)
{
if(!u) return 0;
if(e[u].v==v) {splay(u);return u;}
if(e[u].v>v) return find(v,e[u].ch[0]);
else return find(v,e[u].ch[1]);
} int pre(int v,int u=root)
{
if(!u) return 0;
if(e[u].v==v) {splay(u);return u;}
if(e[u].v>v) return pre(v,e[u].ch[0]);
else
{
int x=pre(v,e[u].ch[1]);
if(!x) return u;
return e[u].v>e[x].v ? u:x;
}
} int post(int v,int u=root)
{
if(!u) return 0;
if(e[u].v==v) {splay(u);return e[u].v;}
if(e[u].v<v) return post(v,e[u].ch[1]);
else
{
int x=post(v,e[u].ch[0]);
if(!x) return u;
return e[u].v<e[x].v ? u:x;
}
} void del(int u)
{
if(!u) return;
splay(u);
if(e[u].ch[0]*e[u].ch[1]==0) root=e[u].ch[0]+e[u].ch[1];
else
{
int t=e[u].ch[1];
while(e[t].ch[0]) t=e[t].ch[0];
e[t].ch[0]=e[u].ch[0];e[e[u].ch[0]].f=t;
root=e[u].ch[1];
}
e[root].f=0;
cnt--;
} int main()
{
int N,ans=0,cmd,x;
scanf("%d",&N);
while(N--)
{
scanf("%d%d",&cmd,&x);
if(!cnt)
{
flag=cmd;
insert(root,x,0);
}
else
{
if(cmd==flag) insert(root,x,0);
else
{
int l=pre(x),r=post(x);
if(!r) ans=(ans+x-e[l].v)%P,del(l);
else if(!l) ans=(ans+e[r].v-x)%P,del(r);
else
{
if(x-e[l].v<=e[r].v-x) ans=(ans+x-e[l].v)%P,del(l);
else ans=(ans+e[r].v-x)%P,del(r);
}
}
}
}
cout<<ans<<endl;
//system("pause >nul");
return 0;
}

洛谷P2596 书架

这道题需要查找第K大以及元素的排名,这个时候需要我们引入siz这个元素,表示以u为根的子树的大小

这个时候同样需要添加一些操作维护:

1、在插入时,对应经过的节点siz++;

2、在删除时,由于u节点splay到了根节点,对其儿子没有影响,但是u的左子树插到了t下,所以需要对t以及t的祖先们的siz都加上u的左儿子siz大小【假装splay操作已自动维护好了siz】

3、在splay操作中,由于我们翻转了节点,树的形态发生了变化,需要维护一下siz的值,只需要添加这一句函数:

inline void up(int u) {e[u].siz=e[e[u].ch[0]].siz+e[e[u].ch[1]].siz+1;}

然后在spin结束时顺序【顺序!】调用up(fa)和up(u)就好了。

如下

inline void spin(int u)
{
int fa=e[u].f,s=isr(u);
e[u].f=e[fa].f;
if(e[fa].f) e[e[fa].f].ch[isr(fa)]=u;
e[fa].f=u;
e[fa].ch[s]=e[u].ch[s^1];
if(e[u].ch[s^1]) e[e[u].ch[s^1]].f=fa;
e[u].ch[s^1]=fa;
up(fa);up(u);
}

有了siz的辅助,我们很快就可以写出插到k大值与查询排名的函数

order

int order(int v,int u=root)
{
if(!u) return INF;
if(e[u].v==v) {splay(u);return e[e[u].ch[0]].siz+1;}
if(e[u].v>v) return order(v,e[u].ch[0]);
return order(v,e[u].ch[1]);
}

kth

kth
int kth(int k,int u=root)
{
if(!u) return INF;
if(k==e[e[u].ch[0]].siz+1) {splay(u);return u;}
if(k<e[e[u].ch[0]].siz+1) return kth(k,e[u].ch[0]);
return kth(k-e[e[u].ch[0]].siz-1,e[u].ch[1]);
}

有了这两个函数的帮助,我们就可以很快写出这道题了【原谅我代码的啰嗦,但还算清晰】

我用了手动给书加上权值值的方法维护书的顺序的

#include<iostream>
#include<cstdio>
#include<algorithm>
#define isr(x) (e[e[x].f].ch[1]==x)
using namespace std;
const int maxn=800005,INF=2000000000;
int big=maxn,small=maxn;
int code[maxn]; inline int read()
{
int out=0,flag=1;char c=getchar();
while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();}
return out*flag;
} class node
{
public:
int id,v,f,siz,ch[2];
node() {ch[0]=ch[1]=0;siz=0;}
//node(int a,int b,int c) {id=a;b=v;f=c;siz=1;ch[0]=ch[1]=0;}
}e[maxn];
int nodei=0,root=0; inline void up(int u) {e[u].siz=e[e[u].ch[0]].siz+e[e[u].ch[1]].siz+1;} inline void spin(int u)
{
int fa=e[u].f,s=isr(u);
e[u].f=e[fa].f;
if(e[fa].f) e[e[fa].f].ch[isr(fa)]=u;
e[fa].f=u;
e[fa].ch[s]=e[u].ch[s^1];
if(e[u].ch[s^1]) e[e[u].ch[s^1]].f=fa;
e[u].ch[s^1]=fa;
up(fa);up(u);
} void splay(int u)
{
while(e[u].f)
{
if(!e[e[u].f].f) spin(u);
else if(isr(u)^isr(e[u].f)) spin(u),spin(u);
else spin(e[u].f),spin(u);
}
root=u;
} void insert(int& u,int v,int id,int fa)
{
if(!u) {e[u=++nodei].id=id;e[u].v=v;e[u].f=fa;e[u].siz=1;splay(u);}
else if(v<e[u].v) e[u].siz++,insert(e[u].ch[0],v,id,u);
else e[u].siz++,insert(e[u].ch[1],v,id,u);
} int find(int v,int u=root)
{
if(!u) return 0;
if(e[u].v==v) {splay(u);return u;}
if(e[u].v>v) return find(v,e[u].ch[0]);
else return find(v,e[u].ch[1]);
} void del(int u)
{
if(!u) return;
splay(u);
if(e[u].ch[0]*e[u].ch[1]==0) root=e[u].ch[0]+e[u].ch[1];
else
{
int p=e[u].ch[1];
while(e[p].ch[0]) p=e[p].ch[0];
e[p].ch[0]=e[u].ch[0];e[e[u].ch[0]].f=p;
while(e[p].f!=u) e[p].siz+=e[e[u].ch[0]].siz,p=e[p].f;
root=e[u].ch[1];
}
e[root].f=0;
} int order(int v,int u=root)
{
if(!u) return INF;
if(e[u].v==v) {splay(u);return e[e[u].ch[0]].siz+1;}
if(e[u].v>v) return order(v,e[u].ch[0]);
return order(v,e[u].ch[1]);
} int kth(int k,int u=root)
{
if(!u) return INF;
if(k==e[e[u].ch[0]].siz+1) {splay(u);return u;}
if(k<e[e[u].ch[0]].siz+1) return kth(k,e[u].ch[0]);
return kth(k-e[e[u].ch[0]].siz-1,e[u].ch[1]);
} void printpre(int u)
{
if(u)
{
printpre(e[u].ch[0]);
printf("%d ",e[u].id);
printpre(e[u].ch[1]);
}
} char cmd[10];
int main()
{
int N=read(),M=read(),x,t,u,p;
for(int i=1;i<=N;i++)
{
x=read();
code[x]=++big;
insert(root,code[x],x,0);
}
while(M--)
{
//printpre(root);cout<<endl;
scanf("%s",cmd);x=read();
switch(cmd[0])
{
case 'T':
del(find(code[x]));
code[x]=--small;
insert(root,code[x],x,0);
break;
case 'B':
del(find(code[x]));
code[x]=++big;
insert(root,code[x],x,0);
break;
case 'I':
t=read();
if(t)
{
u=find(code[x]);
p=e[u].ch[t==1];
while(e[p].ch[t==-1]) p=e[p].ch[t==-1];
code[x]=e[p].v;
code[e[p].id]=e[u].v;
swap(e[u].id,e[p].id);
}
break;
case 'A':
printf("%d\n",order(code[x])-1);
break;
case 'Q':
printf("%d\n",e[kth(x)].id);
break;
default:
break;
}
}
//system("pause >nul");
return 0;
}

splay tree 学习笔记的更多相关文章

  1. 珂朵莉树(Chtholly Tree)学习笔记

    珂朵莉树(Chtholly Tree)学习笔记 珂朵莉树原理 其原理在于运用一颗树(set,treap,splay......)其中要求所有元素有序,并且支持基本的操作(删除,添加,查找......) ...

  2. dsu on tree学习笔记

    前言 一次模拟赛的\(T3\):传送门 只会\(O(n^2)\)的我就\(gg\)了,并且对于题解提供的\(\text{dsu on tree}\)的做法一脸懵逼. 看网上的其他大佬写的笔记,我自己画 ...

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

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

  4. 伸展树(Splay)学习笔记

    二叉排序树能够支持多种动态集合操作,它可以被用来表示有序集合,建立索引或优先队列等.因此,在信息学竞赛中,二叉排序树应用非常广泛. 作用于二叉排序树上的基本操作,其时间复杂度均与树的高度成正比,对于一 ...

  5. Link Cut Tree学习笔记

    从这里开始 动态树问题和Link Cut Tree 一些定义 access操作 换根操作 link和cut操作 时间复杂度证明 Link Cut Tree维护链上信息 Link Cut Tree维护子 ...

  6. [普通平衡树splay]【学习笔记】

    参考: http://blog.csdn.net/clove_unique/article/details/50630280 gty课件 找一个好的风格太难了,自己习惯用struct,就强行用stru ...

  7. 文艺平衡Splay树学习笔记(2)

    本blog会讲一些简单的Splay的应用,包括但不局限于 1. Splay 维护数组下标,支持区间reserve操作,解决区间问题 2. Splay 的启发式合并(按元素多少合并) 3. 线段树+Sp ...

  8. 矩阵树定理(Matrix Tree)学习笔记

    如果不谈证明,稍微有点线代基础的人都可以在两分钟内学完所有相关内容.. 行列式随便找本线代书看一下基本性质就好了. 学习资源: https://www.cnblogs.com/candy99/p/64 ...

  9. k-d tree 学习笔记

    以下是一些奇怪的链接有兴趣的可以看看: https://blog.sengxian.com/algorithms/k-dimensional-tree http://zgjkt.blog.uoj.ac ...

随机推荐

  1. C#之Lambda不得不说的用法

    由于我才开始接触代码的时候遇到循环问题都是用foreach和for,慢慢就成了习惯,不愿意用其他简便的方式,偶然发现lambda能代替循环而且简便了很多.当然我用lambda也不是简便,更多是不用不行 ...

  2. katalon系列十四:执行Windows命令&获取项目路径

    Katalon Studio中也可以运行Windows命令执行一些系统操作. 根据官方文档,在test case中输入命令:cmd = 'del E:\\shot\\*.xlsx E:\\shot\\ ...

  3. VMWARE网络配置内网与外网互ping

    新增网络适配器 设置自定义VMnet0 自动桥接 NAT的网络要配置网关 我们在CentOS中打开ifcfg-ens33文件(每个系统文件名都不同,但都是以ifcfg-ens33开头的文件),进行修改 ...

  4. PytorchZerotoAll学习笔记(二)--梯度下降之手动求导

    梯度下降算法:    待优化的损失值为 loss,那么我们希望预测的值能够很接近真实的值 y_pred ≍ y_label      我们的样本有n个,那么损失值可以由一下公式计算得出: 要使得los ...

  5. java-HttpGetPost-图片字节流上传

    在java程序开发中经常用到与服务端的交互工作,主要的就是传递相应的参数请求从而获取到对应的结果加以处理 可以使用Get请求与Post请求,注意!这里的Get请求不是通过浏览器界面而是在程序代码中设置 ...

  6. 爬虫:Scrapy12 - Stats Collection

    Scrapy 提供了方便的收集数据的机制.数据以 key/value 方式存储,值大多是计数值.该机制叫做数据收集器(Stats Collector),可以通过 Crawler API 的属性 sta ...

  7. Linux学习——操作文件与目录

    1. ls:列出文件及目录信息. 命令格式:ls [选项] ... 常用选项: -a 显示指定目录下所有子目录与文件,包括隐藏文件. -A 显示指定目录下所有子目录与文件,包括隐藏文件.但不列出“.” ...

  8. “Hello World!”团队第十三次会议

    今天是我们团队“Hello World!”团队召开的第十三次会议.博客内容: 一.会议时间 二.会议地点 三.会议成员 四.会议内容 五.todo list 六.会议照片 七.燃尽图 一.会议时间 2 ...

  9. uc浏览器的用户体验

    用户界面: 我认为,uc浏览器的用户界面还是很招人喜欢的,可以很容易让用户找到自己想看的网页.简单快捷. 记住用户的选择: uc在每次用户访问完网站之后都会记住用户访问的高频网站,以便下次用户可以更好 ...

  10. c# 调用c++dll二次总结

    1.pinvoke结构不对称,添加语句(网上有) 2.含回调函数,成员参数的结构体必须完全,尽管自己用不到. 3.加深对c++指针的理解.一般情况下,类型加*等效于c++中的ref.但对于short* ...