Splay学习笔记
来源:互联网 发布:女生可怕经历知乎 编辑:程序博客网 时间:2024/06/05 07:01
昨天这个时候到现在终于把Splay给搞明白了,还A了一道郁闷的出纳员;刚学完的感受:我再也不碰这东西了;做完郁闷的出纳员的感受:我发誓这辈子不当出纳员(虽然这确实只是个入门题……)
于是来讲一讲这个恶心的东西吧……(全程不用指针,请做好心理准备……)
学习前请先学习下二叉搜索树,里面可能直接用到这个东西
首先,Splay是一个数据结构,为了突出它是一个数据结构,所以给他开个结构体……
struct tree{ int val,sz,cnt;//val为值 sz为子树大小,cnt为"有多少这个值" //cnt:假如出现了两个一模一样的值,只需要让cnt+1就可以了,cnt是数目 int s[2],f;//s[0]为左儿子 s[1]为右儿子 f为父亲};
为了之后的操作,我们再写三个函数(如果认为没用可以先跳过,过一会用到了再回来看)
bool son(int x) //返回x是左儿子还是右儿子,如果为左儿子,返回0,右儿子则返回1{ return a[a[x].f].s[1] == x;}void rejs(int x) //重新计算以x为根,子树的大小,子树大小等于左子树大小加右子树大小(好吧我承认我英语拙计){ a[x].sz = a[a[x].s[0]].sz + a[a[x].s[1]].sz + a[x].cnt;}void point(int x,int y,bool z) //在x下面插入y,y是x的z儿子(z为0则左儿子,z为1为右儿子){ a[x].s[z] = y; a[y].f = x;}
写完这几个树的基本函数,我们要开始讲splay啦(似乎我之前好像真的没有开始讲splay)
Splay最重要的一个操作就是旋转(rotate),如下,(图片为1600x900,可直接作为桌面):
我们的目的是将红色节点旋转到它父亲的位置,即黄色节点的位置
我们应当怎么办呢?不要急,看组图
bool型变量son(黄)代表黄色节点是左儿子还是右儿子
将红色节点接到黄色节点的父亲(也就是绿色节点)的下面,是绿色节点的son(黄)儿子,也就是说代替了黄色节点;
黄色节点被代替了怎么办?再开个变量记录一下即可
bool型变量son(红)代表红色节点是左儿子还是右儿子
把红色节点的son(红)&1儿子(图中的蓝色节点,蓝色节点和父亲关系和红色节点和父亲关系正好相反)接到黄色节点下面,是黄色节点是son(红)儿子,代替了红色节点;
最后把黄色节点的父亲改为红色节点,旋转完成!
旋转完成后:
(我再也不用PS画这玩意了……)
void rot(int x){ int p = a[x].f; bool d = son(x); point(a[p].f,x,son(p)); point(p,a[x].s[d^1],d); point(x,p,d^1); rejs(p); rejs(x); if(a[x].f == 0) root = x;//root是根节点的编号}
这就是Splay的核心操作——rotate,它的时间复杂度是——O(1)
然而学会了有什么用呢= =,下面来讲Splay的其他操作及如何平衡
首先是Splay的插入操作,Splay每插入一个新节点,就把这个节点强制旋转到根节点,如何强制旋转到根节点呢?如果while(不是根节点)rotate(x)的话就有可能退化成一条链……于是Splay“贴心”的为我们准备了splay操作……
splay操作的目的是将一个点旋转到根节点,这个操作是这样的:
1.如果这个点是根节点,那么你可以直接退出了……
2.如果这个点的父亲是根节点,那么就直接把这个点rotate上去……
3.如果上述两条均不满足,那么分类讨论:
(1)设son(x)为这个和父亲节点的关系,son(f)为父亲节点和爷爷节点的关系
(2)如果两个关系相同(均为左儿子或均为右儿子),那么就先rotate父亲节点,然后rotate这个节点
(3)如果两个关系不同(一个是左儿子一个是右儿子),那么就把这个节点连续rotate两次
void splay(int x){ while(a[x].f != 0){ if(a[a[x].f].f == 0) rot(x); else { if(son(x) == son(a[x].f)){ rot(a[x].f); rot(x); } else{ rot(x); rot(x); } } }}
这样就可以写出插入操作了,还记得插入操作怎么做吗?插入一个节点然后splay到根节点
void ins(int x){ int w = root,f = 0; int p = findn(x); if(p)//如果这个值已经存在,那么就直接给这个节点+1吧 { a[p].cnt ++; while(p != root) { rejs(p); p = a[p].f; } rejs(root); return ; } while(w) { f = w; if(x < a[w].val) w = a[w].s[0]; else w = a[w].s[1]; } //这是前半部分,和普通的二叉查找树插入方法一样,我承认我打的很丑…… a[++tot].val = x; a[tot].cnt = 1;//新建节点,标号为tot if(f == 0)//如果这个点是根节点的话,直接插入即可…… { root = tot; rejs(root); return ; } //否则插入这个节点并spaly到根节点 if(x < a[f].val) point(f,tot,0); else point(f,tot,1); splay(tot);}
然后是删除操作,Splay的删除操作比较丧病,我只讲一下我的写法吧……
设要删除的节点为x,那么,首先splay一下x的前驱,然后splay一下x的后继,啥?你前驱和后继还不会写???好吧好吧,我讲……
x的前驱是指小于x且最大的节点,后继就是大于x且最小的节点
前驱就是在x的左子树上一直往右跑,如果没有左子树那就往上找
后继就是在x的右子树上一直往左跑,如果没有右子树那也往上找
自行yy可解
int near(int x,bool d){ if(a[x].s[d] != 0){ int p = a[x].s[d]; while(a[p].s[d^1]) p = a[p].s[d^1]; return p; } else if(son(x) == d^1) return a[x].f; else{ int p = a[x].f; while(son(p) == d){ if(p == root) return 0; p = a[p].f; } return a[p].f; }}
接着继续我们的删除操作,删除操作就是上面讲的:splay(x的前驱),splay(x的后继),然后x的后继变为根节点,x的前驱变为根节点的左儿子,x到哪里了呢?
x成为了x前驱的右子树!直接砍掉这个右子树就ok啦~(如果删除区间也可以这样做,splay区间左边界的前驱,splay区间右边界的后继,整个区间就变成了区间左界前驱的右子树~(≧▽≦)/~)
void cle(int x)//cle操作是清除一个点,没有也无所谓吧{ a[a[x].f].s[son(x)] = 0; a[x].val = 0; a[x].sz = 0; rejs(a[x].f); a[x].f = 0;}void del(int x){ int p = near(x,0); int q = near(x,1); if(!p || !q) { splay(x); if(p == 0) { root = a[x].s[1]; a[a[x].s[0]].f = 0; } else { root = a[x].s[0]; a[a[x].s[0]].f = 0; } return ; } splay(p); splay(q); rot(p); cle(x);}
至此你终于获得了一棵可用的splay,拿去乱搞吧!
- zkw Splay学习笔记
- Splay学习笔记
- Splay学习笔记
- Splay Trees 学习笔记
- splay tree 学习笔记
- 【数据结构】Splay学习笔记
- splay 学习笔记
- Splay树 学习笔记一
- splay学习笔记及模板
- 学习笔记--(平衡树)splay
- 伸展树Splay学习笔记
- [学习笔记] bzoj3224 普通平衡树:splay模板
- Splay学习--初篇
- Splay Tree学习过程
- Splay学习总结
- splay学习小记
- Splay学习总结
- 【学习】Splay平衡树
- 20151015笔试小结
- 转自“展望未来,总结过去10年的程序员生涯,给程序员小弟弟小妹妹们的一些总结性忠告”
- C/C++内存管理详解 堆 栈
- java 读取文件 乱码
- html及Dreamweaver学习心得
- Splay学习笔记
- ocp-420
- 一对多关系的问题求助
- IOS各版本兼容性 代码思路
- relay-log命名规则
- 关于BIOS的入口地址0xFFFF0
- ocp-421
- Codevs P3324 新斯诺克
- PHP解决抢购、秒杀、抢楼、抽奖等阻塞式高并发库存防控超量的思路方法