分享一个关于Avl树的迭代器算法
1 研究过程
前段时间在研究avl树的迭代实现,在节点不使用parent指针的情况下,如何使用堆栈来实现双向地迭代。我参考了网络上的大部分迭代器实现,要么是使用了parent指针(就像c++的map容器中的迭代算法),要么就是前中后序遍历,没找到一种真正意义上可以双向迭代的算法,于是乎在我的不屑努力下,基于灵感想到了一个只使用很低层数的堆栈就可以完成双向迭代的算法。
我把它命名为“基于双向堆栈的avl树双向迭代算法”。
这个算法分为三个主要部分:初始化、前向迭代(next)、后向迭代(previous)。下面我将以文字的形式详细说明这三个算法。
2 算法步骤
2.1 初始化算法
1.定义需要使用的变量:一个长度为40的用于存放树节点的指针数组stack,模拟双向堆栈,存放前进或后退方向的节点。2个长度变量,分别是stack_next_len以及stack_prev_len,分别初始化为0和39(stack的长度-1)。以下将stack_next_len统称为前向索引,将stack_prev_len统称为后向索引。前者用于存放前进方向的节点指针,后者用于存放后退方向的节点指针。当前的高度cur_height,用于保存当前所处节点的高度,并用于更新在后续迭代过程中节点的高度。状态量status,表示当前迭代的边界状态。
2.根据用户所选的迭代起始位置(最小值节点或最大值节点),初始化stack。
2.1) 如果从最小值节点开始迭代,那么从树根节点开始,下潜到最左侧节点,并依次将下潜过程中遍历的所有节点从前向索引往后(从0到39的方向)依次存入stack。
2.2) 如果从最大值节点开始迭代,那么从树根节点开始,下潜到最右侧节点,并依次将下潜过程中遍历的所有节点从后向索引往前(从39到0的方向)依次存入stack。
3.根据下潜的方向,修改status,如果是左侧下潜,那么状态为1(表示当前的位置是处于比树的最左侧节点还要小的一个假象相邻节点),如果是右侧下潜,那么状态为2(表示当前的位置是处于比树的最右节点还要大的一个假象相邻节点)。若树为空,则将status设置为3。
2.2 前向迭代的算法
在完成了初始化之后,根据用户传入的迭代方向,调整双向堆栈的内容和迭代器的状态。
以下是执行next(前向)动作的迭代算法:
如果迭代器的status = 0,说明当前的迭代状态正常,没有处于边界条件。接下来判断迭代器所指向节点是否存在右子节点。
1.1) 如果存在,那么将当前节点存入双向堆栈的后向索引处,索引递减1(表现为向双向堆栈的尾部堆入一个节点)。随后将节点设置为其右子节点R,并执行一个循环。该循环的作用表现为:从R节点左向下潜到其最左侧节点LM,并依次更新下潜过程中的节点高度,将它们存入到双向堆栈的前向索引处,然后递增1(表现为向双向堆栈的首部堆入一个节点)。完成后返回LM所指数据。
1.2) 如果不存在,且前向索引的值不为0。那么取出前向索引所处的节点N。设当前节点的高度减去N节点的高度再减去1的值为m,然后将后向索引的值加上m(表现双向堆栈的尾部出栈m个节点)。然后更新迭代器高度为N的高度。完成后返回N所指数据。
1.3) 如果上述都不满足,那么说明节点达到了比二叉树的最右节点还要大的位置(设为limR)。设置status=2,并返回空指针。
如果当前status = 1,那么说明当前的位置处于二叉树最左节点还要小的位置(设为limL),此时进行next操作,那么所指向的节点必定是二叉树的最左节点L。设置迭代器status = 0,表示状态正常,然后返回L所指数据。
如果当前status = 2,那么说明在到达limR之后还在尝试next操作,这种操作是无效的。只需返回空指针。
如果status = 3,直接返回空指针
2.3 后向迭代的算法
后向迭代的算法与前向迭代大致相同,只需要将对应的方向和索引进行交换即可。
以下是执行previous(后向)动作的迭代算法:
如果迭代器的status = 0,说明当前的迭代状态正常,没有处于边界条件。接下来判断迭代器所指向节点是否存在左子节点。
1.1) 如果存在,那么将当前节点存入双向堆栈的前向索引处,索引递减1(表现为向双向堆栈的首部堆入一个节点)。随后将节点设置为其左子节点L,并执行一个循环。该循环的作用表现为:从L节点右向下潜到其最右侧节点RM,并依次更新下潜过程中的节点高度,将它们存入到双向堆栈的后向索引处,然后递减1(表现为向双向堆栈的尾部堆入一个节点)。完成后返回RM所指数据。
1.2) 如果不存在,且后向索引的值不为39(栈的最大高度-1)。那么取出后向索引所处的节点N。设当前节点的高度减去N节点的高度再减去1的值为m,然后将前向索引的值减去m(表现双向堆栈的首部出栈m个节点)。然后更新迭代器高度为N的高度。完成后返回N所指数据。
1.3) 如果上述都不满足,那么说明节点到达了limL(见上文)。设置status=1,并返回空指针。
如果当前status = 2,那么说明当前的位置在limR(见上文),此时进行previous操作,那么所指向的节点必定是二叉树的最右节点R。设置迭代器status = 0,表示状态正常,然后返回R所指数据。
如果当前status = 1,那么说明在到达limL之后还在尝试previous操作,这种操作是无效的。只需返回空指针。
如果status = 3,直接返回空指针
3 算法原理分析
基于堆栈的双向迭代算法,实现难点在于如何保证在双向任意次数迭代的情况下而不会产生节点的丢失以及失序的问题,解决这个问题的核心在于,如何根据前后迭代状态合理地更新两个堆栈的高度。
根据二叉树迭代过程中的下潜方向,我们分别用两个堆栈保存左向下潜和右向下潜迭代过程的所有节点,并且在无法通过下潜获取符合迭代方向的节点时,我们取出自身堆栈顶部的节点。这个节点代表需要回退的位置。如图1所示,假设当前节点是C,那么根据二叉树性质可知,B节点一定位于C节点之后迭代,且A节点一定位于C节点之前迭代。若此时执行previous操作,而节点B位于节点A和节点C之间,那么B节点应该失效。如果当前所处的节点是D,在执行next操作后,应该回退到节点R,并且在R与D之间的节点A和B都应该失效。而根据这一个过程,可以得知,若发生回退,则一定只可能且只有同一个下潜方向的节点需要被消除。而不同下潜方向的节点被保存在了不同堆栈,所以只需更新与自身相对的另一个堆栈的高度即可。且这两个堆栈的高度总和不会超过树高-1的长度,因此可以采用双向堆栈进一步节省空间。

图1 AVL树实例
4 算法扩展
基于上述的算法流程,可以延伸出其他算法的迭代器版本(不考虑多线程)。
首先是查找算法,在需要获取某一个节点并对其附近的节点进行迭代时,只需要把查找路径上的节点,按照下潜的方向依次存入双向堆栈即可。
其次是插入算法,在插入某一个节点之后,只需自顶向下,再次使用迭代器版本的查找算法即可重新构建查找路径。时间复杂度为:
\]
最后是删除算法,如果删除的节点是迭代器所指的节点,那么在执行删除时,只需要将节点高度从小到大,进行合并,然后使用自底向上的删除算法,对树结构进行重构,并且将于删除的节点值最接近的节点更新到迭代器所指节点,然后在回退至根节点的过程中,如果发生旋转操作,只需要根据旋转的类型,调整双向堆栈的结构即可。时间复杂度为:
\]
5 多线程可行性
以下将对多个线程使用同一个avl树对象进行迭代的情况做讨论。
经过理论分析可以得到,在多线程环境下,使用parent指针与使用双向堆栈的迭代算法在进行静态迭代时都不会失效,而使用parent指针的迭代算法,在迭代过程中如果需要进行插入操作,那么只需要加上互斥语句即可。而使用parent指针的迭代算法在删除操作的情况下,有非常低的概率会其他线程的迭代器失效,这种情况是所删除的节点正好被其他迭代器访问。
使用双向堆栈迭代的迭代算法,在进行删除操作以及插入操作时,都会使其他线程的迭代器失效,原因是树结构的改变不会实时更新到其他线程的堆栈上,并且各个迭代器的堆栈状态是不一样的,这样的同步代价就很高。解决方法是,在任意一个线程使用插入或删除操作之后,通知其余正在使用迭代器的进程,从avl树的根节点开始,将迭代器的当前所指数据指针作为查找依据,进行一个模糊查找,查找到最接近的那一个节点,从而完成双向堆栈的重构。使用这种方法时,在删除操作上同样存在与使用parent指针时一样的问题。如果使用的是指针类型来做查找依据,那么在查找时需确保备份了自身数据指针所指的数据,避免出现悬挂指针。这两种操作在有效的情况下,同步的时间复杂度如下:
\]
其中是第i个线程的节点数量。
还可以使用读写锁的方式进一步简化时间复杂度,简化后的时间复杂度如下
\]
其中O(1)表示获取读写锁的时间,后部表示节点数量最大的线程查找一次某个节点的时间。
6 其他探讨
也许会存在其他能够进一步缩短迭代器重新调整时间的实现,也欢迎各位能把自己的看法分享出来,希望能够互相探讨。
源码的链接:https://gitee.com/luqi_866813670/MyLib
分享一个关于Avl树的迭代器算法的更多相关文章
- AVL树的平衡算法(JAVA实现)
1.概念: AVL树本质上还是一个二叉搜索树,不过比二叉搜索树多了一个平衡条件:每个节点的左右子树的高度差不大于1. 二叉树的应用是为了弥补链表的查询效率问题,但是极端情况下,二叉搜索树会无限接近 ...
- AVL树的JAVA实现及AVL树的旋转算法
1,AVL树又称平衡二叉树,它首先是一颗二叉查找树,但在二叉查找树中,某个结点的左右子树高度之差的绝对值可能会超过1,称之为不平衡.而在平衡二叉树中,任何结点的左右子树高度之差的绝对值会小于等于 1. ...
- 分享一个我改进过的ocr算法
https://github.com/zhangbo2008/chineseOCR-jingjianban 欢迎大家前来拍砖
- 数据结构和算法(Golang实现)(28)查找算法-AVL树
AVL树 二叉查找树的树高度影响了查找的效率,需要尽量减小树的高度,AVL树正是这样的树. 一.AVL树介绍 AVL树是一棵严格自平衡的二叉查找树,1962年,发明者Adelson-Velsky和La ...
- 数据结构图文解析之:AVL树详解及C++模板实现
0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...
- 纸上谈兵:AVL树
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 二叉搜索树的深度与搜索效率 我们在树, 二叉树, 二叉搜索树中提到,一个有n个节点 ...
- 纸上谈兵: AVL树[转]
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 二叉搜索树的深度与搜索效率 我们在树, 二叉树, 二叉搜索树中提到,一个有n个节点 ...
- 使用C编程语言实现AVL树
本文将介绍AVL树及其插入.删除操作,最后使用C编程语言实现基于平衡因子(balance factor)的AVL树. 什么是AVL树? AVL树(AVL tree)是前苏联计算机科学家Adelson- ...
- PAT 1066 Root of AVL Tree[AVL树][难]
1066 Root of AVL Tree (25)(25 分) An AVL tree is a self-balancing binary search tree. In an AVL tree, ...
- AVL树,红黑树,B-B+树,Trie树原理和应用
前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作 ...
随机推荐
- CentOS6.5安装与配置JDK-7
系统环境:centos-6.5 安装方式:rpm安装 软件:jdk-7-linux-i586.rpm 下载地址:http://www.oracle.com/technetwork/java/javas ...
- 详细的讲一下微服务框架--SpringCloud
一:微服务架构? 1.为什么需要微服务架构 大家都知道,最开始开发系统就是单机模式(only one computer).但是随着网络的不断发展,数据不断的增加,业务不断的增加,之前在一台机器上实现的 ...
- word文档怎么让封面没有页码,页码从正文开始(word 2019)
1.打开需要插入页码的文档,光标放在正文处的开头,然后点击word窗口中的 [布局] ---> 选择[分隔符] -->选择 [分节符] 下面的 [连续]; 2.然后选择word功能区 ...
- 【深度学习】基于 Alluxio 数据缓存的性能优化
作者 | 车漾(阿里云高级技术专家).顾荣(南京大学 副研究员) 导读:Alluxio 项目诞生于 UC Berkeley AMP 实验室,自开源以来经过 7 年的不断开发迭代,支撑大数据处理场景的数 ...
- 云原生时代 RocketMQ 运维管控的利器 - RocketMQ Operator
作者 | 刘睿.杜恒 导读:RocketMQ Operator 现已加入 OperatorHub,正式进入 Operator 社区.本文将从实践出发,结合案例来说明,如何通过 RocketMQ Ope ...
- [Ethereum] Gas Station Network (GSN) eip-1613 与 Gas Relay Network (GRN) eip-1077
在 Ethereum dapp 中,任何涉及状态改动的交易都需要消耗 Gas,这限制了很多没有钱包或者 ETH 的用户对 dapp 的采用. 理念 让非以太用户能够访问智能合约 (如dapps),允许 ...
- 2019-9-2-C#-设计模式-责任链
title author date CreateTime categories C# 设计模式 责任链 lindexi 2019-09-02 12:57:37 +0800 2018-2-13 17:2 ...
- SQL server 批量删除表
select 'drop table '+name+';' from sys.tables declare c cursor forselect NAME from sysobjects where ...
- 一些有用的css函数
var 使用自定义的属性值. :root { --main-bg-color: pink; } body { background-color: var(--main-bg-color); } att ...
- vue实现左右两列竖直分别滑动,且双向关联的选项卡(二)
查了诸如vant,mint组件上并没有找到期望的这种效果(cube组件上有,但项目中实在不想再引入一个第三方的组件库了),但实际上在移动端app开发中很常见的一个效果.于是按照自己的思路将这个效果做了 ...