【数据结构与算法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), 这种概念称为" ...
随机推荐
- Linux下cat命令的使用
1.普通用法-->查看文件内容 cat file_name 查看文件时的相关参数: 1.cat f1.txt,查看f1.txt文件的内容. 2.cat -n f1.txt,查看f1.txt文件的 ...
- Spring中使用@within与@target的一些区别
目录 背景 模拟项目例子 看看使用@within和@target的区别 @within @target @target 看起来跟合理一点 通知方法中注解参数的值为什么是不一样的 想用@within,但 ...
- 第06课:GDB 常用命令详解(中)
本课的核心内容: info 和 thread 命令 next.step.util.finish.return 和 jump 命令 info 和 thread 命令 在前面使用 info break 命 ...
- wrap()包裹被选元素的内容
<!doctype html><html><head><meta charset="utf-8"><title>修改代码 ...
- Fastjson反序列化漏洞基础
Fastjson反序列化漏洞基础 FastJson是alibaba的一款开源JSON解析库,可用于将Java对象转换为其JSON表示形式,也可以用于将JSON字符串转换为等效的Java对象. 0x0 ...
- 删除数组中指定的元素,然后将后面的元素向前移动一位,将最后一位设置为NULL 。 String[] strs={“aaa”,”ccc”,”ddd”,”eee”,”fff”,”ggg”}; 指定删除字符串“ccc”,把后的元素依次向前移动!!!
public static void main(String[] args) { int temp = -1; String[] strs = {"aaa", "ccc& ...
- python win32com 处理excle 写一个公共的类
利用win32com处理excle,这样可以不管文件是老的xls还是新的xlsx格式,非常方便. 类的源码如下: import win32com.client as win32 class easyE ...
- Selenium系列5-XPath路径表达式
Xpath介绍 XPath 使用路径表达式在 XML 文档中进行导航 XPath 使用路径表达式来选取 XML 文档中的节点或者节点集.这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似. ...
- CI框架页面打开空白,无报错为解决方法新环境
1.打开错误显示,可以在控制controllers的首页入口加入以下代码,查看错误 error_reporting(-1); ini_set('display_errors', 1); //插入显示所 ...
- 获取系统版本,判断是windows还是Linux
package com.foresee.zxpt.common.utils; import java.util.Properties; /** * 获取系统版本 * @author GZ * */ p ...