数据结构与算法Python版 平衡二叉查找树AVL

news2024/12/28 20:25:02

文章目录

  • 一、平衡二叉查找树
  • 二、AVL树测试
  • 三、AVL树-算法分析


一、平衡二叉查找树

平衡二叉查找树-AVL树的定义

  • AVL树:在key插入时一直保持平衡的二叉查找树。
  • 可以利用AVL树实现抽象数据类型映射Map。与二叉查找树相比,AVL树基本上与二叉查找树的实现相同,不同在于生成与维护过程。
  • 平衡因子balance factor:根据节点的左右子树的高度来定义,等于左子树的高度减去右子树的高度
    • 如果平衡因子大于0,称为“左重left-heavy”,
    • 如果平衡因子小于0,称为“右重right-heavy”
    • 如果平衡因子等于0,称为平衡
  • 如果一个二叉查找树中每个节点的平衡因子都在-1,0,1之间,则把这个二叉查找树称为平衡树
  • AVL树的搜索时间复杂度为O(log2n)

AVL树的Python实现

  • _put方法:AVL树同时也是二叉查找树,所以新key必定以叶节点形式插入到AVL树中。插入一个新key后,更新着其父节点到根节点的平衡因子,对平衡因子大于或小于1的节点进行重新平衡

  • rebalance重新平衡方法:将不平衡的子树进行旋转rotation,右重子树进行左旋转,左重子树进行右旋转。以右重子树进行左旋转为例,分两种情况:

    • 情况一:将右子节点B提升为子树的根,将旧根节点A作为新根节点B的左子节点。如果新根节点B原来有左子节点,则将此节点设置为A的右子节点(A的右子节点一定有空)。
      在这里插入图片描述

    • 情况二:在左旋转之前检查右子节点的因子。如果右子节点“左重”的话,先对它进行右旋转,再实施原来的左旋转
      在这里插入图片描述

from my_tree import TreeNode, BinarySearchTree


class AVLNode(TreeNode):
    def __init__(self, key, val, l_child=None, r_child=None, parent=None, b_factor=0):
        self.key = key
        self.val = val
        self.l_child: AVLNode = l_child
        self.r_child: AVLNode = r_child
        self.parent: AVLNode = parent
        self.b_factor = b_factor


class AVLTree(BinarySearchTree):
    def put(self, key, val):
        """插入key val,构造BST"""
        # 如果树不为空时,调用_put()插入到BST;否则成为BST的根
        if self.root:
            self._put(key, val, self.root)
        else:
            self.root = AVLNode(key, val)

        self.size += 1

    def _put(self, key, val, cur_node: TreeNode):
        # 如果key比currentNode小,那么_put到左子树;否则,_put到左子树。
        if key < cur_node.key:
            if cur_node.has_l_child():
                self._put(key, val, cur_node.l_child)
            else:
                cur_node.l_child = AVLNode(key, val, parent=cur_node)
                # 增加:调整因子
                self.update_balance(cur_node.l_child)
        else:
            if cur_node.has_r_child():
                self._put(key, val, cur_node.r_child)
            else:
                cur_node.r_child = AVLNode(key, val, parent=cur_node)
                # 增加:调整因子
                self.update_balance(cur_node.r_child)

    def update_balance(self, node: AVLNode):
        if node.b_factor > 1 or node.b_factor < -1:
            # 重新平衡
            self.re_balance(node)
            return

        if node.parent != None:
            if node.is_l_child():
                node.parent.b_factor += 1
            elif node.is_r_child():
                node.parent.b_factor -= 1

            # 递归调用:调整父节点因子
            if node.parent.b_factor != 0:
                self.update_balance(node.parent)

    def re_balance(self, node: AVLNode):
        if node.b_factor < 0:  # 右重需要左旋
            if node.r_child.b_factor > 0:
                self.rotate_right(node.r_child)  # 右子节点先右旋
                self.rotate_left(node)
            else:
                self.rotate_left(node)
        elif node.b_factor > 0:
            if node.l_child.b_factor < 0:
                self.rotate_left(node.l_child)
                self.rotate_right(node)
            else:
                self.rotate_right(node)

    def rotate_left(self, rot_root: AVLNode):
        new_root = rot_root.r_child
        rot_root.r_child = new_root.l_child
        if new_root.l_child != None:
            new_root.l_child.parent = rot_root
        new_root.parent = rot_root.parent

        if rot_root.is_root():
            self.root = new_root
        else:
            if rot_root.is_l_child():
                rot_root.parent.l_child = new_root
            else:
                rot_root.parent.r_child = new_root
        new_root.l_child = rot_root
        rot_root.parent = new_root
        rot_root.b_factor = rot_root.b_factor + 1 - min(new_root.b_factor, 0)
        new_root.b_factor = new_root.b_factor + 1 + max(rot_root.b_factor, 0)

    def rotate_right(self, rot_root: AVLNode):
        new_root = rot_root.l_child
        rot_root.l_child = new_root.r_child
        if new_root.r_child != None:
            new_root.r_child.parent = rot_root
        new_root.parent = rot_root.parent

        if rot_root.is_root():
            self.root = new_root
        else:
            if rot_root.is_r_child():
                rot_root.parent.r_child = new_root
            else:
                rot_root.parent.r_child = new_root
        new_root.r_child = rot_root
        rot_root.parent = new_root
        rot_root.b_factor = rot_root.b_factor + 1 - min(new_root.b_factor, 0)
        new_root.b_factor = new_root.b_factor + 1 + max(rot_root.b_factor, 0)

二、AVL树测试

  • 依次将下面6个节点插入到平衡二叉查找树,查看平衡二叉查找树状态

在这里插入图片描述

  • 删除节点26,查看平衡二叉查找树状态

在这里插入图片描述

  • 参考资料:AVL树在线演示。在线演示网站,在删除节点时使用的是中序前驱(左子树中的最大节点),所示演示结果与输出结果有差异。
bst = AVLTree()
bst[17] = "tiger"
bst[26] = "dog"
bst[31] = "cow"
bst[54] = "cat"
bst[93] = "lion"
bst[77] = "bird"

bst.display_tree()
print(bst[26])
del bst[26]
print(bst[26])
bst.display_tree()


### 输出结果
54 26 17 31 93 77 
dog
None
54 31 17 93 77

三、AVL树-算法分析

  • AVL树始终维持平衡,get方法始终保持O(log2n)最佳性能
  • AVL树的put方法分为两个部分:
    • 需要插入的新节点是叶节点,更新其所有父节点和祖先节点的代价最多为O(log2n)。
    • 如果插入的新节点引发了不平衡,重新平衡最多需要2次旋转,但旋转的代价与问题规模无关,是常数O(1)
    • put方法的时间复杂度还是O(log2n)

映射的实现方法小结

  • 抽象数据类型映射Map,可以采用多种数据结构和算法来实现,时间复杂度数量级如下
操作类型有序表散列表二叉查找树AVL树
putO(n)O(1) -> O(n)O(log2n) -> O(n)O(log2n)
getO(log2n)O(1) -> O(n)O(log2n) -> O(n)O(log2n)
inO(log2n)O(1) -> O(n)O(log2n) -> O(n)O(log2n)
delO(n)O(1) -> O(n)O(log2n) -> O(n)O(log2n)

您正在阅读的是《数据结构与算法Python版》专栏!关注不迷路~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2267091.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Redis】Redis 安装与启动

在实际工作中&#xff0c;大多数企业选择基于 Linux 服务器来部署项目。本文演示如何使用 MobaXterm 远程连接工具&#xff0c;在 CentOS 7 上安装和启动 Redis 服务&#xff08;三种启动方式&#xff0c;包括默认启动、指定配置启动和开机自启&#xff09;。在安装之前&#x…

通过Js动态控制Bootstrap模态框-弹窗效果

目的&#xff1a;实现弹出窗、仅关闭弹窗之后才能操作&#xff08;按ESC可退出&#xff09;。自适应宽度与高度、当文本内容太多时、添加滚动条效果。 效果图 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">…

el-table合并单元行后的多选框选中问题

问题描述 合并单元行以后&#xff0c;首列的多选框也会合并&#xff0c;此时选中该多选框其实是只选中了合并单元行的第一行的多选框&#xff0c;其他的都未被选中。 解决方案 原本想着手动去修改表头的半选状态和全选状态 &#xff0c;但是没有找到相关方法&#xff0c;后面觉…

电脑缺失libcurl.dll怎么解决?详解电脑libcurl.dll文件丢失问题

一、libcurl.dll文件丢失的原因 libcurl.dll是一个用于处理URL传输的库文件&#xff0c;广泛应用于各种基于网络的应用程序。当这个文件丢失时&#xff0c;可能会导致相关应用程序无法正常运行。以下是libcurl.dll文件丢失的一些常见原因&#xff1a; 软件安装或卸载不完整&a…

图文教程:使用PowerDesigner导出数据库表结构为Word/Html文档

1、第一种情况-无数据库表&#xff0c;但有数据模型 1.1 使用PowerDesigner已完成数据建模 您已经使用PowerDesigner完成数据库建模&#xff0c;如下图&#xff1a; 1.2 Report配置和导出 1、点击&#xff1a;Report->Reports&#xff0c;如下图&#xff1a; 2、点击&…

UE--如何用 Python 调用 C++ 及蓝图函数

前言 先讲下如何用 Python 调用 C 函数吧。 详细可见我的上篇文章 最关键的一点就是得在函数上加一个宏&#xff1a;UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable) static bool GetOrCreatePackage(const FString& PackagePath, UPackage*& OutPackag…

小程序租赁系统开发的优势与实践探索

内容概要 小程序租赁系统开发正在引起广泛关注&#xff0c;特别是在数字化快速发展的今天。很多企业开始意识到&#xff0c;小程序不仅能为他们带来更多的客户&#xff0c;还能极大地提高管理效率。借助小程序&#xff0c;用户在租赁时可以更加方便地浏览和选择产品&#xff0…

闲谭Scala(3)--使用IDEA开发Scala

1. 背景 广阔天地、大有作为的青年&#xff0c;怎么可能仅仅满足于命令行。 高端大气集成开发环境IDEA必须顶上&#xff0c;提高学习、工作效率。 开整。 2. 步骤 2.1 创建工程 打开IDEA&#xff0c;依次File-New-Project…&#xff0c;不好意思我的是中文版&#xff1a;…

富芮坤FR800X系列之PWM输出程序应用设计

文章目录 前言1.设计背景2.简介3.如何设计控制调光的接口呢4.硬件设计5.软件设计5.1.软件流程图5.2.软件代码 6.小结 前言 版权归作者所有、未经允许、请勿转载。 读者对象&#xff1a; 本文档主要适用以下工程师&#xff1a; 嵌入式系统工程师 单片机软件工程师 IOT固…

node-js Express防盗链

什么是防盗连 一个简单的说明&#xff0c;假如在前端img标签想要引用图片网站上的图片&#xff0c;当你将图片地址放到img标签上想要显示的时候你发现&#xff0c;图片显示不了&#xff0c;这说明网站采用了防盗链。 怎么实现的呢 在请求头中一般会有 Referer&#xff0c;它…

使用ArcGIS/ArcGIS pro绘制六边形/三角形/菱形渔网图

在做一些尺度分析时&#xff0c;经常会涉及到对研究区构建不同尺度的渔网进行分析&#xff0c;渔网的形状通常为规则四边形。构建渔网的方法也很简单&#xff0c;使用ArcGIS/ArcGIS Pro工具箱中的【创建渔网/CreateFishnet】工具来构建。但如果想构建其他形状渔网进行相关分析&…

RabbitMQ工作模式(详解 工作模式:简单队列、工作队列、公平分发以及消息应答和消息持久化)

文章目录 十.RabbitMQ10.1 简单队列实现10.2 Work 模式&#xff08;工作队列&#xff09;10.3 公平分发10.4 RabbitMQ 消息应答与消息持久化消息应答概念配置 消息持久化概念配置 十.RabbitMQ 10.1 简单队列实现 简单队列通常指的是一个基本的消息队列&#xff0c;它可以用于…

nexus docker安装

#nexus docker 安装 docker pull sonatype/nexus3 mkdir -p /data/nexus-data docker run -itd -p 8081:8081 --privilegedtrue --name nexus3 \ -v /data/nexus-data:/var/nexus-data --restartalways docker.io/sonatype/nexus3 #访问 http://192.168.31.109:8081/ 用户名&am…

ADC(二):外部触发

有关ADC的基础知识请参考标准库入门教程 ADC&#xff08;二&#xff09;&#xff1a;外部触发 1、TIM1的CC1事件触发ADC1DMA重装载2、TIM3的TRGO事件(的更新事件)触发ADC1DMA重装载3、TIM3的TRGO事件(的捕获事件)触发ADC1DMA重装载4、优化TIM3的TRGO事件(的捕获事件)触发ADC1D…

【产品应用】一体化无刷电机在旋转等离子喷枪中的应用

在现代工业制造与加工领域&#xff0c;等离子喷枪凭借其高温、高速的等离子射流&#xff0c;能够实现高效的材料表面处理、切割以及焊接等工艺&#xff0c;在众多行业中发挥着关键作用。而一体化无刷电机的应用&#xff0c;更是为等离子喷枪的性能提升和稳定运行注入了强大动力…

ElasticSearch - 深入解析 Elasticsearch Composite Aggregation 的分页与去重机制

文章目录 Pre概述什么是 composite aggregation&#xff1f;基本结构after 参数的作用问题背景&#xff1a;传统分页的重复问题after 的设计理念响应示例 after 如何确保数据不重复核心机制Example步骤 1: 创建测试数据创建索引插入测试数据 步骤 2: 查询第一页结果查询第一页返…

易基因: BS+ChIP-seq揭示DNA甲基化调控非编码RNA(VIM-AS1)抑制肿瘤侵袭性|Exp Mol Med

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 肝细胞癌&#xff08;hepatocellular carcinoma&#xff0c;HCC&#xff09;早期复发仍然是一个具有挑战性的领域&#xff0c;其中涉及的机制尚未完全被理解。尽管微血管侵犯&#xff08…

顶会评测集解读-AlignBench: 大语言模型中文对齐基准

评测集社区 CompssHub 作为司南 OpenCompass大模型评测体系的重要组成部分&#xff0c;致力于简化并加快研究人员和行业人士搜索和使用评测集的过程。评测集社区 CompssHub 目前已收录了学科、推理、知识、代码等12个方向的评测集&#xff0c;欢迎大家探索。 为了将评测集社区…

量子退火与机器学习(1):少量数据求解未知QUBO矩阵,以少见多

文章目录 前言ー、复习QUBO&#xff1a;中药配伍的复杂性1.QUBO 的介入&#xff1a;寻找最佳药材组合 二、难题&#xff1a;QUBO矩阵未知的问题1.为什么这么难&#xff1f; 三、稀疏建模(Sparse Modeling)1. 欠定系统中的稀疏解2. L1和L2的选择&#xff1a; 三、压缩感知算法(C…

Linux应用软件编程-多任务处理(线程)

线程&#xff1a;轻量级的进程&#xff0c;线程的栈区独立&#xff08;8M&#xff09;&#xff0c;与同一进程中的其他线程共用进程的堆区&#xff0c;数据区&#xff0c;文本区。 进程是操作系统资源分配的最小单位&#xff1b;线程是cpu任务调度的最小单位。 1. 线程的创建…