平衡搜索树——AVL树小记

news2025/1/21 22:20:17

文章目录

  • 二叉搜索树
  • 平衡搜索树
    • AVL树
      • 定义
        • AVL中平衡(Balance)因子的定义
      • AVL树插入规则
        • AVL树失衡情况
          • 左左失衡/右右失衡
          • 左右失衡
          • RL失衡
      • 代码
        • 左旋-调整平衡
        • 插入
        • 调整平衡因子
        • AVL树正确性的验证

二叉搜索树

在这里插入图片描述
理想情况下,二叉搜索树的查找时间复杂度是0(log(n))
但是,在退化的情况下,变成0(n),进而导致插入/删除的时间复杂度也退化为0(n)

为了解决上述问题,引入了平衡树的概念

严格:高度不会“相差”
宽泛:高度不会“相差”太多

由此,使得查找、插入删除等复杂度不会太高。

平衡搜索树

AVL树

定义

二叉 平衡 搜索 树

AVL树在二叉搜索树的基础上要求:任意结点,左右子树的高度差的绝对值不能超过1
高度差= [-1,0,1]

AVL中平衡(Balance)因子的定义

任取树中的结点,要求结点左子树的高度和结点右子树的高度的高度差的绝对值不能超过1
在这里插入图片描述

AVL树插入规则

1.按照普通搜索树的插入规则进行结点的插入
在这里插入图片描述
2.随着结点的插入,导致BF出现了变化,所以需要进行平衡因子的调节
在这里插入图片描述
没变之前的BF(parent)的值可能是?

我们这棵树插入之前,这棵树得是(MUST)是一棵AVL树->得满足AVL树的特征
-1、0、1

其中一条,任取结点,BF(结点)=-1、0、1

  • 当BF(parent)=-1 情况时: 插入结点在parent左子树
    在这里插入图片描述
    BF(parent)=0,因此是AVL树,不需要调节平衡因子
  • 当BF(parent)=0 情况时, 插入后平衡因子变为1或-1;变为1的情况如下所示:
    在这里插入图片描述
    BF(parent)++,BF(parent.parent)因此++,需调整调节平衡因子(如图中3部分循环判断是否调整)。
  • 当BF(parent)=1/-1 情况时, 插入后平衡因子变为2或-2
    在这里插入图片描述
    子树失衡,需要对其执行旋转操作,来调节平衡
    在这里插入图片描述

AVL树失衡情况

失衡一般为如下四种情况:

在这里插入图片描述

调整规则

LL:对 parent做右旋,插入结束!
LR:先对node做左旋,再对parent做右旋,插入结束!RR:对 parent做左旋,插入结束!
RL:先对node做右旋,再对parent做左旋,插入结束!

左左失衡/右右失衡

失衡子树的parent不需要进行调整。右右失衡同理推导。
在这里插入图片描述
结论
在这里插入图片描述

左右失衡

在这里插入图片描述
假设丁的高度为x,则失衡后每个子树高度分析如下
在这里插入图片描述
该分析对之后分情况讨论理解较为重要

左右失衡总共有三种

在这里插入图片描述

情况二的乙子树和丙子树的高度分析有点绕,类似反证,排除不符合整体树失衡条件的情况,得出情况二中乙丙子树高度为x、x-1;
在这里插入图片描述

其中,上述中的3小点中不平衡指的是,BF(parent),即左右失衡第一个图中的a结点。这种情况下在没插入新节点,BF(a)=2就已经失衡了,所以不符合插入失衡条件。

LR失衡三种情况总结:

在这里插入图片描述

RL失衡

RL失衡三种情况总结——类似LR画图分析

在这里插入图片描述

代码

左旋-调整平衡

通过左旋的图进行结合分析,可更利于理解代码
在这里插入图片描述

// 以 m 为结点,进行旋转
    private void leftRotate(AVLNode m) {
        // m 代表图中的 b 结点
        // parent 代表 b 结点可能存在的父亲
        AVLNode parent = m.parent;
        // right 代表图中的 a 结点
        AVLNode right = m.right;
        // leftOfRight 代表图中的可能存在的乙子树的根结点
        AVLNode leftOfRight = right.left;
        /*
        其中: m != null && right != null
        但是: parent 不保证 !null, leftOfRight 不保证 !null
         */

        right.parent = parent;  // 蓝色线的关系
        // 黑色线的关系
        if (parent == null) {
            // m 是 root
            root = right;
        } else {
            if (m == parent.left) {
                parent.left = right;
            } else {
                parent.right = right;
            }
        }

        right.left = m; // 黑色线的关系
        m.parent = right;   // 蓝色线的关系

        m.right = leftOfRight;
        if (leftOfRight != null) {
            leftOfRight.parent = m;
        }
    }

左旋单元测试测试用例

在这里插入图片描述

插入

public class AVLTree {
    public AVLNode root = null;
    public int size = 0;    // 保存树的结点个数

    public void insert(long key) {
        AVLNode node = new AVLNode();
        node.key = key;
        node.left = null;
        node.right = null;
        node.parent = null;
        node.bf = 0;

        if (root == null) {
            root = node;
            size++;
            return;
        }

        AVLNode current = root;
        AVLNode parent = null;
        while (current != null) {
            if (key == current.key) {
                return;
                //throw new RuntimeException("插入失败,key 有重复: " + key);
            } else if (key < current.key) {
                parent = current;
                current = current.left;
            } else {
                parent = current;
                current = current.right;
            }
        }

        node.parent = parent;//...............
        if (key < parent.key) {
            parent.left = node;
        } else {
            parent.right = node;
        }

        avlAdjust(parent, node);

        size++;
    }
}

调整平衡因子

private void avlAdjust(AVLNode parent, AVLNode node) {
        // parent != null && node != null

        while (true) {
            // 进行平衡因子的调整
            if (node == parent.left) {
                parent.bf++;
            } else {
                parent.bf--;
            }

            // 第一种情况
            if (parent.bf == 0) {
                return;
            }

            // 第二种情况
            if (parent.bf == -1 || parent.bf == 1) {
                node = parent;
                parent = parent.parent;

                if (parent == null) {
                    // 向上回溯到根的位置了
                    return;
                }
                continue;
            }

            // 情况三
            // parent.bf == -2 || parent.bf == 2
            break;
        }

        // 一定是出现失衡情况了
        if (parent.bf == 2) {
            if (node.bf == 1) {
                // LL 失衡
                rightRotate(parent);

                parent.bf = node.bf = 0;
            } else {
                // LR 失衡
                // node.bf == -1
                AVLNode c = node.right;
                int condition;
                if (parent.right == null) {
                    condition = 1;
                } else if (c.bf == 1) {
                    condition = 2;
                } else {
                    condition = 3;
                }

                leftRotate(node);
                rightRotate(parent);

                if (condition == 1) {
                    parent.bf = node.bf = c.bf = 0;
                } else if (condition == 2) {
                    parent.bf = -1;
                    node.bf = c.bf = 0;
                } else {
                    parent.bf = c.bf = 0;
                    node.bf = 1;
                }
            }
        } else {
            // parent.bf == -2
            if (node.bf == -1) {
                // RR 失衡
                leftRotate(parent);

                parent.bf = node.bf = 0;
            } else {
                // RL 失衡
                // node.bf == 1

                AVLNode c = node.left;
                int condition;
                if (parent.left == null) {
                    condition = 1;
                } else if (c.bf == 1) {
                    condition = 2;
                } else {
                    condition = 3;
                }

                rightRotate(node);
                leftRotate(parent);

                if (condition == 1) {
                    parent.bf = node.bf = 0;
                } else if (condition == 2) {
                    parent.bf = c.bf = 0;
                    node.bf = -1;
                } else {
                    parent.bf = 1;
                    node.bf = c.bf = 0;
                }
            }
        }
    }

AVL树正确性的验证

1.整棵树是一棵搜索树<=>中序遍历是有序的
⒉.整棵树是一棵平衡树<=>每个结点的 bf in (-1,0,1)&&每个结点的bf 计算正确

public class Main {
    public static void main(String[] args) {
        //test1();
        test2();
    }

    private static void test2() {
        for (int i = 0; i < 10; i++) {
            Random random = new Random();
            AVLTree tree = new AVLTree();
            for (int j = 0; j < 1_0000; j++) {
                int key = random.nextInt(10_0000);
                tree.insert(key);
            }

            validate(tree);
        }
    }

    private static void validate(AVLTree tree) {
        validateIsSearchTree(tree);
        validateIsBalanceTree(tree);
        System.out.println("这棵树是 AVL 树");
    }

    private static int heightAndCheckBF(AVLNode root) {
        if (root == null) {
            return 0;
        }

        int leftHeight = heightAndCheckBF(root.left);
        int rightHeight = heightAndCheckBF(root.right);

        if (root.bf != (leftHeight - rightHeight)) {
            throw new RuntimeException("有结点 bf 计算不正确");
        }

        if (root.bf != -1 && root.bf != 0 && root.bf != 1) {
            throw new RuntimeException("有结点,左右子树高度差的绝对值超过了 1,不是平衡树");
        }

        return Integer.max(leftHeight, rightHeight) + 1;
    }

    private static void validateIsBalanceTree(AVLTree tree) {
        heightAndCheckBF(tree.root);
        System.out.println("所有结点的 bf 都计算正确,并且都在范围内,是平衡树");
    }

    private static void validateIsSearchTree(AVLTree tree) {
        List<Long> inorderKeys = new ArrayList<>();
        inorderSaveKey(inorderKeys, tree.root);
        // inorderKeys 中保存的中序遍历后所有 key

        // 复制出得到的所有 key,对得到的 key 进行排序
        List<Long> keysSorted = new ArrayList<>(inorderKeys);
        Collections.sort(keysSorted);

        if (keysSorted.equals(inorderKeys)) {
            System.out.println("中序遍历是有序的,说明是搜索树");
        } else {
            throw new RuntimeException("中序遍历是无序的,说明不是搜索树");
        }
    }

    private static void inorderSaveKey(List<Long> inorderKeys, AVLNode root) {
        if (root != null) {
            inorderSaveKey(inorderKeys, root.left);
            inorderKeys.add(root.key);
            inorderSaveKey(inorderKeys, root.right);
        }
    }


    private static void test1() {
        List<Integer> list = Arrays.asList(2, 15, 8, 3, 4, 6, 9, 7, 17, 20, 19, 14);

        AVLTree tree = new AVLTree();
        for (Integer key : list) {
            tree.insert(key);
        }

        preorder(tree.root);
        System.out.println();
        inorder(tree.root);
        System.out.println();
    }

    private static void preorder(AVLNode node) {
        if (node != null) {
            System.out.printf("(%d, %d) ", node.key, node.bf);
            preorder(node.left);
            preorder(node.right);
        }
    }

    private static void inorder(AVLNode node) {
        if (node != null) {
            inorder(node.left);
            System.out.printf("(%d, %d) ", node.key, node.bf);
            inorder(node.right);
        }
    }
}

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

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

相关文章

Linux 进程概念 —— 初识操作系统(OS)

文章目录1. 概念2. 设计操作系统的目的3. 定位4. 如何理解管理5. 再谈操作系统&#x1f351; 硬件部分&#x1f351; 操作系统&#x1f351; 驱动程序&#x1f351; 用户部分&#x1f351; 系统调用接口&#x1f351; 用户接口操作6. 总结1. 概念 任何计算机系统都包含一个基本…

HTML5基础汇总

目录 一&#xff0c;html5文档头部 1.页面标题及字符集的收集 &#xff08;1&#xff09;.title标签 &#xff08;2&#xff09;.charset属性 2.元信息的设置 &#xff08;1&#xff09;.meta标签的作用 &#xff08;2&#xff09;.http-equiv/content &#xff08;2&am…

数据结构堆介绍,图文详解分析——Java/Kotlin双版本代码

堆介绍 堆是一种特殊的树结构。根据根节点的值与子节点值的大小关系&#xff0c;堆又分为最大堆和最小堆。 最大堆&#xff1a;每个节点的值总是大于或者等于其任意子节点的值。所以最大堆中根节点即为最大值。 最小堆&#xff1a;每个节点的值总是小于或者等于其任意子节点…

第六章课后题(LSTM | GRU)

目录习题6-3 当使用公式(6.50)作为循环神经网络得状态更新公式时&#xff0c;分析其可能存在梯度爆炸的原因并给出解决办法.习题6-4 推导LSTM网络中参数的梯度&#xff0c;并分析其避免梯度消失的效果​编辑习题6-5 推导GRU网络中参数的梯度&#xff0c;并分析其避免梯度消失的…

集合框架----源码解读Vector篇

1.vector官方简绍 Vector类实现了一个可增长的对象数组。与数组一样&#xff0c;它包含可以使用整数索引访问的组件。但是&#xff0c;Vector的大小可以根据需要增加或缩小&#xff0c;以适应在创建Vector之后添加和删除项。 每个向量都试图通过维护一个容量和一个capacityIncr…

C#设计模式详解(2)——Factory Method(工厂方法)

文章目录C#设计模式详解(2)——Factory Method&#xff08;工厂方法&#xff09;工厂方法模式1.1 概念1.2 意图1.3 问题1.4 解决方案1.5 工厂方法模式结构1.6 案例代码1.7 游戏开发中的应用C#设计模式详解(2)——Factory Method&#xff08;工厂方法&#xff09; 工厂方法模式…

领悟《信号与系统》之 非周期信号的傅里叶变换

非周期信号的傅里叶变换一、非周期信号的傅里叶变换二、 典型信号的傅立叶变换1.单边指数信号2.偶双边指数3. 矩阵脉冲信号4. 奇双边指数5. 符号函数6. 冲激信号7. 阶跃信号三、常用傅里叶变换表这里记录的信号都是非周期信号的傅里叶变化&#xff0c;频谱变换的特点就是&#…

【计算机网络】数据链路层:使用广播信道的数据链路层(1)

局域网的数据链路层 局域网特点&#xff1a;网络为一个单位所拥有&#xff0c;地理范围和站点数目均有限。 地理范围和站点数目均有限。 局域网优点&#xff1a; 具有广播功能&#xff0c;从一个站点可以很方便地访问全网。 便于系统的拓展和演变&#xff0c;各设备的位置…

Discourse 论坛激活邮件问题

根据 Discourse 的官方推荐&#xff0c;我们使用的是 MailGun 的服务。 在大部分情况下都没有问题&#xff0c;但是在一些特定的邮件地址&#xff0c;例如 iCloud&#xff0c;我们在发送激活邮件的时候有提示为&#xff1a; "message": "5.5.1 Error: need MA…

实验:温湿度数据oled显示

OK,本次介绍一个oled实验 本来只想做oled实验的 后面想想这个实验太简单 就加上了温湿度传感器 oled可以打印英文和数字,比如用display.println(“Hello World!”)就可以了 如果打印汉字就比较复杂了 需要相应的软件,生成编码 然后一个字一个字打印 不过只要汉字不…

【数据结构】树——二叉树

1.树的介绍以及树的基本概念和性质 2.二叉树介绍以及二叉树的性质 3.二叉树的构建&#xff1a;穷举创建&#xff0c;递归创建 4.二叉树的基本操作 之前我们介绍了顺序表&#xff0c;链表&#xff0c;以及栈和队列&#xff0c;这几种数据结构都属于线性结构&#xff0c;而我们接…

GreenPlum/PostGreSQL表锁处理

GreenPlum/PostGreSQL表锁处理 数据库中遇到表锁的情况&#xff0c;可以通过select * from pg_stat_activity;查看表锁的进程及进程ID&#xff0c;从而取消进程&#xff0c;解锁。 一、模拟表锁 1.1 模拟表数据 创建lock_test表&#xff0c;并随意插入一条数据&#xff0c;…

Oracle自治事务示例演示

自治事务 自治事务&#xff08;Pragma autonomous_transaction&#xff09;&#xff1a;是PL/SQL块中的一个单独事务&#xff0c;与调用或触发自己的事务之间互不干扰&#xff0c;自己commit和rollback不会影响其他事务&#xff0c;也不会被其他事务所影响。 通俗的讲&#xff…

【Android App】在线语音识别功能实现(使用云知声平台与WebSocket 超详细 附源码)

需要源码和相关资源请点赞关注收藏后评论区留下QQ~~~ 一、在线语音识别 云知声的语音识别同样采用WebSocket接口&#xff0c;待识别的音频流支持MP3和PCM两种格式&#xff0c;对于在线语音识别来说&#xff0c;云知声使用JSON串封装报文&#xff0c;待识别的音频以二进制形式发…

webpack打包vue文件+gulp打包sass文件

webpack打包vue文件 1,下载依赖 npm i vue-loader npm i webpack-cli2&#xff0c;编写webpack配置文件 /*** 关于webpack的配置文件*/const path require(path)const { VueLoaderPlugin } require(vue-loader)const glob require(glob) // node自带的读取文件的库 /*** …

会多门编程语言的你,最推荐哪3-5门语言?

如果你还想在编程的路上继续提高&#xff0c;那我建议你至少学习4种编程语言。可用的编程语言有很多&#xff0c;所以选择一种感兴趣的学习就可以了。我这么建议的原因是&#xff0c;要掌握编程&#xff0c;建立信心&#xff0c;提高能力&#xff0c;最简单的办法就是学习多种编…

浅析工作流调度器Azkaban

title: Azkaban系列 第一章 概述 1.1 为什么需要工作流调度器 1、一个完整的数据分析系统通常都是由大量任务单元组成&#xff1a; shell 脚本程序&#xff0c;java 程序&#xff0c;mapreduce 程序、hive 脚本等 2、各任务单元之间存在时间先后及前后依赖关系 3、为了很好地…

TIA西门子博途V18安装教程及注意事项

TIA西门子博途V18安装教程及注意事项 前提条件: TIA Portal V18需要.Net Framework 3.5环境,所以在安装TIA V18之前要先安装它。大家可以在控制面板中的程序和功能中检查是否已经安装,如果没有,可以参考以下步骤自行安装: 操作系统&#x

jsp旅行网系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 旅行网系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql&#xff0c;使用java语…

[附源码]Python计算机毕业设计房屋租赁系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…