定义

  • 能够在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树)的更多相关文章

  1. 【数据结构与算法Python版学习笔记】引言

    学习来源 北京大学-数据结构与算法Python版 目标 了解计算机科学.程序设计和问题解决的基本概念 计算机科学是对问题本身.问题的解决.以及问题求解过程中得出的解决方案的研究.面对一 个特定问题,计 ...

  2. 【数据结构与算法Python版学习笔记】目录索引

    引言 算法分析 基本数据结构 概览 栈 stack 队列 Queue 双端队列 Deque 列表 List,链表实现 递归(Recursion) 定义及应用:分形树.谢尔宾斯基三角.汉诺塔.迷宫 优化 ...

  3. 【数据结构与算法Python版学习笔记】树——二叉查找树 Binary Search Tree

    二叉搜索树,它是映射的另一种实现 映射抽象数据类型前面两种实现,它们分别是列表二分搜索和散列表. 操作 Map()新建一个空的映射. put(key, val)往映射中加入一个新的键-值对.如果键已经 ...

  4. 【数据结构与算法Python版学习笔记】树——利用二叉堆实现优先级队列

    概念 队列有一个重要的变体,叫作优先级队列. 和队列一样,优先级队列从头部移除元素,不过元素的逻辑顺序是由优先级决定的. 优先级最高的元素在最前,优先级最低的元素在最后. 实现优先级队列的经典方法是使 ...

  5. 【数据结构与算法Python版学习笔记】递归(Recursion)——定义及应用:分形树、谢尔宾斯基三角、汉诺塔、迷宫

    定义 递归是一种解决问题的方法,它把一个问题分解为越来越小的子问题,直到问题的规模小到可以被很简单直接解决. 通常为了达到分解问题的效果,递归过程中要引入一个调用自身的函数. 举例 数列求和 def ...

  6. 【数据结构与算法Python版学习笔记】树——相关术语、定义、实现方法

    概念 一种基本的"非线性"数据结构--树 根 枝 叶 广泛应用于计算机科学的多个领域 操作系统 图形学 数据库 计算机网络 特征 第一个属性是层次性,即树是按层级构建的,越笼统就越 ...

  7. 【数据结构与算法Python版学习笔记】树——二叉树的应用:解析树

    解析树(语法树) 将树用于表示语言中句子, 可以分析句子的各种语法成分, 对句子的各种成分进行处理 语法分析树 程序设计语言的编译 词法.语法检查 从语法树生成目标代码 自然语言处理 机器翻译 语义理 ...

  8. 【数据结构与算法Python版学习笔记】树——树的遍历 Tree Traversals

    遍历方式 前序遍历 在前序遍历中,先访问根节点,然后递归地前序遍历左子树,最后递归地前序遍历右子树. 中序遍历 在中序遍历中,先递归地中序遍历左子树,然后访问根节点,最后递归地中序遍历右子树. 后序遍 ...

  9. 【数据结构与算法Python版学习笔记】查找与排序——散列、散列函数、区块链

    散列 Hasing 前言 如果数据项之间是按照大小排好序的话,就可以利用二分查找来降低算法复杂度. 现在我们进一步来构造一个新的数据结构, 能使得查找算法的复杂度降到O(1), 这种概念称为" ...

随机推荐

  1. 洛谷P1925 最大划分乘积的数学解法

    题目 最大划分乘积 题解 这道题用到一点导数和数论的知识,很容易看出这道题是求函数 \[f(x)=(\frac{n}{x})^{x} \] ( \(x\) 为正整数)的最大值.我们可以对 \(ln(f ...

  2. centos7安装privoxy

    本文分为三部分,第一部分是在阿里云的ECS上安装Privoxy,第二部分是在AWS的EC2上安装Privoxy,第三部分是Privoxy的配置. 第一部分:阿里云ECS安装Privoxy 配置yum源 ...

  3. 离线安装Windows Terminal

    Windows Terminal颜值高.适配好.速度快,是Windows 10下命令行工具的不二选择. 最近在公司电脑上安装Windows Terminal时遇到一个问题,由于公司电脑不能直接连接外网 ...

  4. L2TP协议简介

    传送门:L2TP代码实现 1. L2TP 概述 L2TP(Layer 2 Tunneling Protocol,二层隧道协议)是 VPDN(Virtual Private Dial-up Networ ...

  5. 修改statefulset 有些不允许直接修改

    1.比如修改一个storageClassName: hostpath   会报错 意思是除了 'replicas', 'template',  'updateStrategy'   其他部分都是不可以 ...

  6. Loadrunner拼装唯一值方法

    由于Loadrunner函数有限性,唯一值需要几个函数的字符串进行拼装,可实现流水号.订单号等等数值的唯一性.具体可见下列方法: 方法一: char OraderID[15];srand(time{N ...

  7. PHP的OpenSSL加密扩展学习(三):证书操作

    关于对称和非对称的加密操作,我们已经学习完两篇文章的内容了,接下来,我们就继续学习关于证书的生成. 生成 CSR 证书签名请求 CSR 是用于生成证书的签名请求,在 CSR 中,我们需要一些 dn 信 ...

  8. DEDE整合套件实现本地多个网站随意切换的开发环境

    一.修改WEB全局配置: 在Listen 80 后面添加自己的端口号. 例如,2020是我的端口 Listen 2020 二.修改WEB站点配置: a---在NameVirtualHost *:80后 ...

  9. c++ 的学习 第二集函数的重载之3 -利用IDA分析bebug里面

    1. 对项目右击,在文件资源管理器中打开文件夹 2.看debug里面的.exe     这个文件 函数的真实的名字 打开.exe文件就是还是显示,,, 3.debug模式有太多的断点信息还有许多不精简 ...

  10. P7405-[JOI 2021 Final]雪玉【二分】

    正题 题目链接:https://www.luogu.com.cn/problem/P7405 题目大意 \(n\)个点在坐标轴上,\(q\)次每次所有点向一个方向移动若干步,每个点的权值是它第一次覆盖 ...