【数据结构与算法Python版学习笔记】树——平衡二叉搜索树(AVL树)
定义
- 能够在key插入时一直保持平衡的二叉查找树: AVL树
- 利用AVL树实现ADT Map, 基本上与BST的实现相同,不同之处仅在于二叉树的生成与维护过程
平衡因子
AVL树的实现中, 需要对每个节点跟踪“平衡因子balance factor”参数
\(balance Factor=height (left SubTree)-height(right SubTree)\)
- 平衡因子大于0,称为“左重left-heavy”,
- 小于零称为“右重right-heavy”
- 平衡因子等于0,则称作平衡。
如果一个二叉查找树中每个节点的平衡因子都在-1, 0, 1之间, 则把这个二叉搜索树称为平衡树
- 在平衡树操作过程中, 有节点的平衡因子超出此范围, 则需要一个重新平衡的过程
AVL树的性能
问题规模(总节点数N)和比对次数(树的高度h)之间的关系
最差情形下的性能:即平衡因子为1或者-1
- 当高度为h时,节点数Nh是:Nh=1+Nh-1+Nh-2
- 与斐波那契数列很相似,随着斐波那契数列的增长,Fi/Fi-1逐渐逼近黄金分割比例Φ
- 最多搜索次数h和规模N的关系, 可以说AVL树的搜索时间复杂度为O(log n)
实现
- 首先, 作为BST, 新key必定以叶节点形式插入到AVL树中
- 叶节点的平衡因子是0, 其本身无需重新平衡
- 但会影响其父节点的平衡因子:
- 作为左子节点插入,则父节点平衡因子会增加1;
- 作为右子节点插入,则父节点平衡因子会减少1。
- 这种影响可能随着其父节点到根节点的路径一直传递上去, 直到:
- 传递到根节点为止;
- 或者某个父节点平衡因子被调整到0,不再影响上层节点的平衡因子为止
重新定义_put方法即可
def _put(self, key, val, currentNode):
if key < currentNode.key:
if currentNode.hasLeftChild():
self._put(key, val, currentNode.leftChild)
else:
currentNode.leftChild = TreeNode(key, val, parent=currentNode)
# 调整因子
self.updateBalance(currentNode.leftChild)
else:
if currentNode.hasRightChild():
self._put(key, val, currentNode.rightChild)
else:
currentNode.rightChild = TreeNode(key, val, parent=currentNode)
# 调整因子
self.updateBalance(currentNode.rightChild)
def updateBalance(self, node):
if node.balanceFactor > 1 or node.balanceFactor < -1:
self.rebalance(node)
if node.parent != None:
if node.isLeftChild():
node.parent.balanceFactor += 1
if node.isRightChild():
node.parent.balanceFactor-+1
if node.parent.balanceFactor != 0:
updateBalance(node.parent)
rebalance重新平衡
- 主要手段 :将不平衡的子树进行旋转rotation
- 视“左重”或者“右重”进行不同方向的旋转
左旋包括以下步骤:
- 将右子节点(节点B)提升为子树的根节点。
- 将旧根节点(节点A)作为新根节点的左子节点。
- 如果新根节点(节点B)已经有一个左子节点,将其作为新左子节点(节点A)的右子节点。注意,因为节点B之前是节点A的右子节点,所以此时节点A必然没有右子节点。因此,可以为它添加新的右子节点,而无须过多考虑。
def rotateLeft(self, rotRoot):
newRoot = rotRoot.rightChild
rotRoot.rightChild = newRoot.leftChild
if newRoot.leftChild != None:
newRoot.leftChild.parent = rotRoot
newRoot.parent = rotRoot.parent
if rotRoot.isRoot():
self.root = newRoot
else:
if rotRoot.isLeftChild():
rotRoot.parent.leftChild = newRoot
else:
rotRoot.parent.rightChild = newRoot
newRoot.leftChild = rotRoot
rotRoot.parent = newRoot
rotRoot.balanceFactor = rotRoot.balanceFactor + \
1-min(newRoot.balanceFactor, 0)
newRoot.balanceFactor = newRoot.balanceFactor + \
1+max(rotRoot.balanceFactor, 0)
右旋步骤如下。
- 将左子节点(节点C)提升为子树的根节点。
- 将旧根节点(节点E)作为新根节点的右子节点。
- 如果新根节点(节点C)已经有一个右子节点(节点D),将其作为新右子节点(节点E)的左子节点。注意,因为节点C之前是节点E的左子节点,所以此时节点E必然没有左子节点。因此,可以为它添加新的左子节点,而无须过多考虑。
如何调整平衡因子
def rebalance(self,node):
# 右重左旋
if node.balanceFactor<0:
# 右子节点左重右旋
if node.rightChild.balanceFactor>0:
self.rotateRight(node.rightChild)
self.rotateLeft(node)
else:
self.rotateLeft(node)
# 左重右旋
elif node.balanceFactor>0:
# 左子节点右重左旋
if node.leftChild.balanceFactor<0:
self.rotateLeft(node.leftChild)
self.rotateRight(node)
else:
self.rotateRight(node)
结语
- 经过复杂的put方法, AVL树始终维持平衡, get方法也始终保持O(log n)高性能
- 整个put方法的时间复杂度还是O(log n)
- 需要插入的新节点是叶节点,更新其所有父节点和祖先节点的代价最多为O(log n)
- 如果插入的新节点引发了不平衡,重新平衡最多需要2次旋转,但旋转的代价与问题规模无关,是常数O(1)
【数据结构与算法Python版学习笔记】树——平衡二叉搜索树(AVL树)的更多相关文章
- 【数据结构与算法Python版学习笔记】引言
学习来源 北京大学-数据结构与算法Python版 目标 了解计算机科学.程序设计和问题解决的基本概念 计算机科学是对问题本身.问题的解决.以及问题求解过程中得出的解决方案的研究.面对一 个特定问题,计 ...
- 【数据结构与算法Python版学习笔记】目录索引
引言 算法分析 基本数据结构 概览 栈 stack 队列 Queue 双端队列 Deque 列表 List,链表实现 递归(Recursion) 定义及应用:分形树.谢尔宾斯基三角.汉诺塔.迷宫 优化 ...
- 【数据结构与算法Python版学习笔记】树——二叉查找树 Binary Search Tree
二叉搜索树,它是映射的另一种实现 映射抽象数据类型前面两种实现,它们分别是列表二分搜索和散列表. 操作 Map()新建一个空的映射. put(key, val)往映射中加入一个新的键-值对.如果键已经 ...
- 【数据结构与算法Python版学习笔记】树——利用二叉堆实现优先级队列
概念 队列有一个重要的变体,叫作优先级队列. 和队列一样,优先级队列从头部移除元素,不过元素的逻辑顺序是由优先级决定的. 优先级最高的元素在最前,优先级最低的元素在最后. 实现优先级队列的经典方法是使 ...
- 【数据结构与算法Python版学习笔记】递归(Recursion)——定义及应用:分形树、谢尔宾斯基三角、汉诺塔、迷宫
定义 递归是一种解决问题的方法,它把一个问题分解为越来越小的子问题,直到问题的规模小到可以被很简单直接解决. 通常为了达到分解问题的效果,递归过程中要引入一个调用自身的函数. 举例 数列求和 def ...
- 【数据结构与算法Python版学习笔记】树——相关术语、定义、实现方法
概念 一种基本的"非线性"数据结构--树 根 枝 叶 广泛应用于计算机科学的多个领域 操作系统 图形学 数据库 计算机网络 特征 第一个属性是层次性,即树是按层级构建的,越笼统就越 ...
- 【数据结构与算法Python版学习笔记】树——二叉树的应用:解析树
解析树(语法树) 将树用于表示语言中句子, 可以分析句子的各种语法成分, 对句子的各种成分进行处理 语法分析树 程序设计语言的编译 词法.语法检查 从语法树生成目标代码 自然语言处理 机器翻译 语义理 ...
- 【数据结构与算法Python版学习笔记】树——树的遍历 Tree Traversals
遍历方式 前序遍历 在前序遍历中,先访问根节点,然后递归地前序遍历左子树,最后递归地前序遍历右子树. 中序遍历 在中序遍历中,先递归地中序遍历左子树,然后访问根节点,最后递归地中序遍历右子树. 后序遍 ...
- 【数据结构与算法Python版学习笔记】查找与排序——散列、散列函数、区块链
散列 Hasing 前言 如果数据项之间是按照大小排好序的话,就可以利用二分查找来降低算法复杂度. 现在我们进一步来构造一个新的数据结构, 能使得查找算法的复杂度降到O(1), 这种概念称为" ...
随机推荐
- SQLServer数据实时同步PostgreSQL
SQLServer数据实时同步至PostgreSQL 前言: 为迎合工作需求有时候传送的数据保存在SQLServer中但由于工作需要需要保存到PostgreSQL中进行处理,本文主要通过在SQLSer ...
- Java 登录模块设计
登录流程 前端登录传输用户名和md5加密后的密码 后端对密码在进行md5加密,或者使用md5加密的密码 + id 进行盐加密,增加密码被破解的难度. 登录成功后,这里分成单体,或者分布式的情况 单体 ...
- 查看所有日志命令:journalctl
journalctl命令作用:实时查看所有日志(内核日志和应用日志) 语法格式: journalctl [参数] 常用参数:-k 查看内核日志-b 查看系统本次启动的日志-u 查看指定服务的日志-n ...
- 这款打怪升级的小游戏,7 年前出生于 GitHub 社区,如今在谷歌商店有 8 万人打了满分
今天我在 GitHub 摸鱼寻找新的"目标"时,发现了一个开源项目是 RougeLike 类的角色扮演游戏「破碎版像素地牢」(Shattered Pixel Dungeon)类似魔 ...
- Python - 面向对象编程 - 实战(5)
前言 主要是针对静态方法.类方法.实例方法.类属性.实例属性的混合实战 需求 设计一个 Game 类 属性 定义一个类属性 top_score 记录游戏的历史最高分,这个属性很明显只跟游戏有关,跟实例 ...
- Typora画各类流程图、甘特图、饼图等详细文档
Draw Diagrams With Markdown August 15, 2016 by typora.io Typora supports some Markdown extensions fo ...
- shell脚本中select循环语句用法
shell脚本中select循环语句 1. 脚本中select的语法格式 select VAR in LIST do command1 command2 ... ... commandN done s ...
- 加入Erlang社区-指引
国内暂且没有发现较活跃.人气较高的论坛或者社区,偶然发现Erlang官网的Community页面描述了一个Slack交流平台,里面有众多异国他乡的大佬,感兴趣的.有技术疑问的都可以加入看看. 加入教程 ...
- select后给字段起别名,where和group后不能用,但having后可以
为什么mysql having的条件表达式可以直接使用select后的别名? SQL语句的语法顺序: FROM -> WHERE -> GROUP BY -> HAVING -> ...
- Set代码
现有一整数集(允许有重复元素),初始为空.我们定义如下操作:add x 把 x 加入集合del x 把集合中所有与 x 相等的元素删除ask x 对集合中元素x的情况询问 对每种操作,我们要求进行如下 ...