伸展树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,这道题目是伸展树的第二题,对于伸展树的各项操作有了更多的理解,这题不同于上一题的用指针表示整 ...
随机推荐
- 【Swift】Runtime动态性分析
Swift是苹果2014年发布的编程开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序.Swift已经开源,目前最新版本为2.2.我们知道Objec ...
- iOS开发-简单的循环结构分析
1.while循环 while (循环条件) { 循环体: } // 1.定义循环变量 ; // 2.循环条件 ) { // 3.循环体 printf("%d\n" ...
- 项目Beta冲刺Day3
项目进展 李明皇 今天解决的进度 完善了程序的运行逻辑(消息提示框等) 明天安排 前后端联动调试 林翔 今天解决的进度 向微信官方申请登录验证session以维护登录态 明天安排 继续完成维护登录态 ...
- bzoj 4373 算术天才⑨与等差数列
4373: 算术天才⑨与等差数列 Time Limit: 10 Sec Memory Limit: 128 MBhttp://www.lydsy.com/JudgeOnline/problem.ph ...
- python识别验证码——PIL,pytesser,pytesseract的安装
1.使用Python识别验证码需要安装Python的图像处理模块(PIL.pytesser.pytesseract) (安装过程需要pip,在我的Python中已经安装pip了,pip的安装就不在赘述 ...
- Docker Mysql主从同步配置搭建
Docker Mysql主从同步配置搭建 建立目录 在虚拟机中建立目录,例如路径/home/mysql/master/data,目录结构如下: Linux中 新建文件夹命令:mkdir 文件夹名 返回 ...
- ELK学习总结(2-4)bulk 批量操作-实现多个文档的创建、索引、更新和删除
bulk 批量操作-实现多个文档的创建.索引.更新和删除 ----------------------------------------------------------------------- ...
- Linux上 ps 命令的用法
ps a 显示现行终端机下的所有程序,包括其他用户的程序.2)ps -A 显示所有程序. 3)ps c 列出程序时,显示每个程序真正的指令名称,而不包含路径,参数或常驻服务的标示. 4)ps -e 此 ...
- python Django注册页面显示头像
python Django注册页面显示头像(views) def register(request): ''' 注册 :param request: :return: ''' if request.m ...
- python——re模块
python--re模块 一 正则表达式的作用 1.给字符串进行模糊匹配, 2.对象就是字符串 二 字符匹配(普通字符.元字符) 普通字符:数字字符和英文字母和自身匹配 2.元字符:. ^ $ * + ...