【数据结构与算法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), 这种概念称为" ...
随机推荐
- HashSet的存储原理
HashSet的底层用哈希散列表来存储对象(默认长度为16的数组),假如: Set set=new HashSet(); set.add(obj); 内部存储过程为:定义h=obj.hashCode, ...
- Gram-Schmidt图像融合
遥感图像融合的定义是通过将多光谱低分辨率的图像和高分辨率的全色波段进行融合从而得到信息量更丰富的遥感图像.常用的遥感图像融合方法有Brovey\PCA\Gram-Schmidt方法.其中Gram-Sc ...
- Cython 模块扩展 - 编程语言 替代实现 应用领域 汇总一览
Python 本身只是一种编程语言规范,可以使用其它编程语言实现它或扩展它:譬如:采有 Python C Java .Net 等重实现 Python,而采用 Python C/C++ C# Java ...
- 性能测试必备命令(4)- pstree
性能测试必备的 Linux 命令系列,可以看下面链接的文章哦 https://www.cnblogs.com/poloyy/category/1819490.html 介绍 显示进程树 语法格式 ps ...
- Vue element keyup.enter失效不起作用
解决方式一 添加按键修饰符@keyup.enter.native 解决方式二 把事件绑定到父元素(外框),需注意多个input问题 <div @keyup.enter="login&q ...
- Redis核心原理与实践--列表实现原理之ziplist
列表类型可以存储一组按插入顺序排序的字符串,它非常灵活,支持在两端插入.弹出数据,可以充当栈和队列的角色. > LPUSH fruit apple (integer) 1 > RPUSH ...
- 推荐一个pycharm验证xpath表达式的插件XPathView + XSLT
使用Appium进行自动化测试,使用xpath元素定位,想验证xpath定位是否正确,可以使用在线的xpath验证网站,也可以使用这次推荐的插件XPathView + XSLT.
- Linux 清理缓存
1. free -m 命令可以查看内存使用情况 2. sync :因为系统在操作的过程当中,会把你的操作到的文件资料先保存到buffer中去,因为怕你在操作的过程中因为断电等原因遗失数据,所以在 ...
- verifycode验证码模版
# -*- coding:utf-8 -*- from django.shortcuts import HttpResponse def verifycode(request): # 引入绘图模块 f ...
- ul li 标签
去除制表符: li{ list-style:none; } ul{ /* 靠左*/ padding-left: 5px; }