伸展树Splay【非指针版】
·伸展树有以下基本操作(基于一道强大模板题:codevs维护队列):
a[]读入的数组;id[]表示当前数组中的元素在树中节点的临时标号;fa[]当前节点的父节点的编号;c[][]类似于Trie,就是一个邻接表,存储左右儿子编号;sum[]区间和;size[]当前根节点所在区间的大小;v[]节点权值;mx[]当前区间连续和最大值;rx[]当前区间右端点连续和最大值lx[]当前区间左端点连续和最大值;rev[]区间反转的LAZY操作;tag[]区间整体赋值修改的LAZY操作;
①“旋学”:旋转操作
使整个树结构保持平衡,使得查询、遍历复杂度始终很优秀(比如说,一棵树变成链后,就变成O(n)复杂度,让人感到遗憾——但是伸展树几乎每一个操作都要进行旋转操作,这使得上述不幸情况不会出现)
有了左旋必有右旋,两两组合成了双旋,它们构成了伸展树的旋转操作。
伸展树许多操作依赖于SPLAY函数,即在一个while()中不断进行双旋。
SPLAY函数依赖于单旋函数:ROTATE(now_node,ch[][])
由于使用了“&”,使得旋转后当前根节点的父亲的其中一个儿子指针可以自然地指向改变后的根节点(x);整个程序体现两个特点:
(1)改变调整亲子关系(2)分类讨论:(如图,以右旋为例)
·我们需要反复记忆的是:旋转操作的目的就是将某一节点旋转到根节点(在区间操作时,也可以旋转至根节点的右儿子处)。
这样看来,SPLAY注定也是承袭上面ROTATE函数的特点。
(如果有些细节不清晰,可参见下文部分解释和完整代码)
②各类操作的基础函数
·接下来是区间操作,像区间平移,区间插入,区间删除等这样的操作,使得伸展树在不仅具备线段树所有的功能,还可高效完成一些其他的任务。
·谈及区间操作,我们有:单点修改-区间查询和区间修改-区间查询。对于后者,LAZY操作是必不可少的,所以伸展树的代码中会有和线段树的相似之处:PUSHUP函数与PUSHDOWN函数(但大多时候PUSHUP函数由于简洁,便直接写在其它函数里面了)。
现在可以从考虑这样两个关键问题入手【伸展树主要是用什么操作完成了所有的区间操作?】【PUSHDOWN函数是在哪些时候调用?】
·第一个问题的解答是:
·首先要了解伸展树的本质——平衡树。说明左右两边节点之间存在大小关系等,当我们将要进行操作的数组的下标作为建树关键字的时候,此时的伸展树才有了维护区间的作用。如图:
·因此:由于2,9号节点不可能每次恰好都在我们想要的位置,所以我们要做的是通过某种操作,在保证树的有序性的前提下,将所求区间的一个端点移动到整棵树的根节点上,另一个端点移动到根节点的右(左)儿子上。这样的话,所求的区间就是根节点的右(左)儿子节点的左(右)子树了。
·把这种方法称为【收口袋】:找到区间左右端点,通过旋转操作,将其移至根节点和根节点的一个儿子上(毫无疑问,这巧妙的利用了SPLAY操作不会改变平衡树性质的特点,让我们要处理的区间被“夹”在两个边界节点之间)。
·在程序中,用SPLIT函数(分离函数)来完成特定区间的收口袋操作。
若设该区间的长度为tot,则有:
这里就自然引出了FIND函数,用于寻找当前权值的点在树中的编号(注意此编号不同于数组下标,再加之后来的区间删减操作,这两者是无法直接关联的,这里是一个美妙的易错点)FIND函数如下:(利用区间长度size来查询)
·有趣的是,FIND函数在树关键字为数值大小时,可以用来求第K大数的具体数值,许多代码中将其改称为Kth_Find函数。
·注意到一个有关第二个问题的细节:FIND函数中出现了PUSHDOWN()。
FIND只是一个查询位置的函数,SIZE[]的值在建树的时候是定下来了的,一般情况下不会改变,所以FIND函数的结果不会受到LAZY操作的影响。为什么在这里要写上一个PUSHDOWN函数?这有助于我们更加深刻的理解该函数的作用——它的使用前提是:①在向下搜索中,需要使用节点信息(比如sum等),那么必须调用PUSHDOWN函数。②在伸展树中,由于FIND函数用于返回某一节点的编号,随之而来的很可能是旋转操作(【收口袋】),我们画图便可明白:在一个节点x多次旋转到达root的过程中,路径上的点的亲子关系,以及随之而来的一系列信息来源(来自左右儿子的信息)都会发生改变,并且除这些点的其他点不会受到任何影响。所以,这些均可作为在哪里调用PUSHDOWN函数的依据。(图为一种PUSHDOWN举例)
·以上面几个操作为基础,引出几个伸展树的区间操作。
③区间删除操作
【将原数列[L,R]删除(后面自动补齐)】
思路十分清晰,利用SPLIT函数【收口袋】,将要删除的序列分离成一棵完整的子树,然后将这棵树的与其根节点的关系断掉。为了节省空间,常常用回收函数REC()将删掉的所有节点放入一个队列中,在将来建树的时候(如果有区间插入操作,那么还会多次建树)利用这些空余的空间。(如图)
· 通过接下来的区间插入操作,可以进一步理解REC函数的作用。
④区间插入操作
【将一个新数列插入到原数列k号元素的后面】
基本步骤是将插入的数列建立一棵新树即BUILD函数,然后用FIND函数找到原数列中k,k+1的位置,最后旋转收口袋,把新树放入口袋中就可以了。
从第4行可以看出,新加入的元素先把以前删除余下的空间给它,用完后在开新的空间给它。注意理解的是,BUILD函数建树过程中,由于多次建树,所以除了判断左右儿子用数组下标x外,其余信息存储都依靠id[x]完成。
(l==r下面包含的部分是本题的处理,可以略过)
·需要再次强调的是,只要一个点的儿子们的信息发生了改变,那么要立刻进行UPDATE(PUSHUP)操作。
⑤区间修改操作
【将某一区间全部变成一个数k】
与线段树差不多:
⑥区间反转操作
【将[L,R]内的数字翻转】
基本思路是【收口袋】后找到该区间的根节点,然后交换两棵子树位置,注意使用LAZY操作(代码中是REV[]数组保存)
tag[]是区间修改的LAZY数组,所以当tag[]不为零时,说明这一段区间会被改成相同的数,那么就不需要翻转了。
⑦区间交换操作(区间循环平移操作)
【本质是交换两个区间的位置,但后者需要一些处理,它的意思是将区间[L,R]中的数向某一固定方向循环平移t次】
举例:[1,2,3,4,5],t=2,那么向右循环平移,则结果为:[4,5,1,2,3]。所以发现这就是一个区间交换操作,断点在R-t+1处。由于t可能会很大,所以我们需要mod处理。思路是先将后面的区间【收口袋】,然后找到前面区间的左端点,进行一个区间插入操作(但无需建树,因为本来就存在这棵树)。
⑧寻找前驱后继操作
【常用来求数据波动大小等问题】
这可以算是比较简单的了。
不是每一句话都有意义,不是每一场梦都有结局......不是每次祈祷都能解脱,不是每次放逐都能救赎。
————汪峰《再见蒲公英》
伸展树Splay【非指针版】的更多相关文章
- 高级搜索树-伸展树(Splay Tree)
目录 局部性 双层伸展 查找操作 插入操作 删除操作 性能分析 完整源码 与AVL树一样,伸展树(Splay Tree)也是平衡二叉搜索树的一致,伸展树无需时刻都严格保持整棵树的平衡,也不需要对基本的 ...
- K:伸展树(splay tree)
伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(lgN)内完成插入.查找和删除操作.在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使 ...
- 树-伸展树(Splay Tree)
伸展树概念 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造. (01) 伸展树属于二 ...
- 纸上谈兵: 伸展树 (splay tree)[转]
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 我们讨论过,树的搜索效率与树的深度有关.二叉搜索树的深度可能为n,这种情况下,每 ...
- 【BBST 之伸展树 (Splay Tree)】
最近“hiho一下”出了平衡树专题,这周的Splay一直出现RE,应该删除操作指针没处理好,还没找出原因. 不过其他操作运行正常,尝试用它写了一道之前用set做的平衡树的题http://codefor ...
- 伸展树(Splay tree)的基本操作与应用
伸展树的基本操作与应用 [伸展树的基本操作] 伸展树是二叉查找树的一种改进,与二叉查找树一样,伸展树也具有有序性.即伸展树中的每一个节点 x 都满足:该节点左子树中的每一个元素都小于 x,而其右子树中 ...
- SplayTree伸展树的非递归实现(自底向上)
Splay Tree 是二叉查找树的一种,它与平衡二叉树.红黑树不同的是,Splay Tree从不强制地保持自身的平衡,每当查找到某个节点n的时候,在返回节点n的同时,Splay Tree会将节点n旋 ...
- [Splay伸展树]splay树入门级教程
首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角.. 首先引入一下splay的概念,他的中文名是伸展树,意思差不多就是可以随意翻转的二叉树 PS:百度百科中伸展树读作:BoGa ...
- ZOJ 3765 Lights (zju March I)伸展树Splay
ZJU 三月月赛题,当时见这个题目没辙,没学过splay,敲了个链表TLE了,所以回来好好学了下Splay,这道题目是伸展树的第二题,对于伸展树的各项操作有了更多的理解,这题不同于上一题的用指针表示整 ...
随机推荐
- JAVA中最容易让人忽视的基础。
可能很多找编程工作的人在面试的时候都有这种感受,去到一个公司填写面试试题的时候,多数人往往死在比较基础的知识点上.不要奇怪,事实就是如此一般来说,大多数公司给出的基础题大概有122道,代码题19道左右 ...
- nyoj 孪生素数
孪生素数问题 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 写一个程序,找出给出素数范围内的所有孪生素数的组数.一般来说,孪生素数就是指两个素数距离为2,近的不能再 ...
- ThinkPad安装deepin操作系统报错解决方法
目前deepin操作系统,软件也比较多,所以想在自己的thinkpad t430笔记本上安装.但是安装时报错,具体错误忘了看了.反复试了好几次都不行,最后在网上查了,讲bios设置调整之后可以正常安装 ...
- 关于TomCat上传文件中文名乱码的问题
最近在学习TomCat文件上传这一部分,由于文件上传必须要三个条件: 1.表单提交方式必须为Post 2.表单中需要有<input type="file">元素,还需要 ...
- javascript原型链__proto__属性的理解
在javascript中,按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头.一个方法使用new操作符创建,例如下面代码块中的Person1(可以吧Person1看做 ...
- Python内置函数(51)——hasattr
英文文档: hasattr(object, name) The arguments are an object and a string. The result is True if the stri ...
- Python Tornado初学笔记之表单与模板(一)
Tornado中的表单和HTML5中的表单具有相同的用途,同样是用于内容的填写.只是不同的是Tornado中的表单需要传入到后台,然后通过后台进行对模板填充. 模板:是一个允许嵌入Python代码片段 ...
- 新概念英语(1-39)Don't drop it!
新概念英语(1-39)Don't drop it! Where does Sam put the vase in the end ? A:What are you going to do with t ...
- java子类重写父类的要点
子类不能重写父类的静态方法,私有方法.即使你看到子类中存在貌似是重写的父类的静态方法或者私有方法,编译是没有问题的,但那其实是你重新又定义的方法,不是重写.具体有关重写父类方法的规则如下:重写规则之一 ...
- final类与final方法
inal---用于类.方法前. final类---不可被继承. final方法---不可被覆盖. final类不能被继承. 如果我们不希望一个类被继承,我们使用final来修饰这个类.这个类将无法被继 ...