算法7:平衡二叉树(AVLTree)

news2024/11/16 1:59:01

二叉排序树(BST, Binary Sort Tree)也称二叉查找树(Binary Search Tree), 或二叉搜索树。

定义:一颗二叉树,满足以下属性:

  • 左子树的所有的值小于根节点的值
  • 右子树的所有值大于根节点的值
  • 左、右子树满足以上两点

    那么我们如何去构建一个自己的二叉排序树呢?算法实现思路如下:

  二叉排序树只是对值进行了排序,但是如果我们对二叉排序树进行树的平衡操作,即对树进行旋转,那么就引出了本文的重要知识点,平衡二叉树(AVL Tree)。《大话数据结构》是这样解释的:

 

 下面通过几张图片来对AVL树有一个基本的认识:

为什么图2,图3不是平衡二叉树呢?

平衡二叉树首先是一颗二叉排序树,因为图2节点值58,它的左节点值为59,不满足二叉排序树的条件,因此更不可能是平衡二叉树了。

图3中,节点58的右子树为空,而左子树的高度为2,不满足左右节点高度差至多为1的条件,因此也不是平衡二叉树

下面通过过右旋,左右旋来做一个简单的介绍。(左旋和右旋相反,左右旋和右左旋相反)

右旋:即对树的根节点重新判定,即左子树最下层的左节点新增节点值,那么可以通过将新生成的根节点往右拉,即可实现平衡。即将新的根节点40拉到旧的根节点50的位置。

 左右旋:如下图中所展示,先对左子树进行左旋,将原来左子树根节点20变为30,即将30往左拉; 然后再进行右旋,即将整颗树的新的根节点30往右拉

  下面直接接上代码,实现AVL Tree的构造

package code.code_04;

public class AVLTree {
    //节点
    public class Node {
        int data; //数据

        Node left; //左子节点
        Node right;//右子节点
        int height; // 记录该节点所在的高度

        public Node(int data) {
            this.data = data;
            this.height = 1; //只要右节点,那么当前节点的高度就为1.
        }
    }

    public static void printTree(Node root) {
        System.out.println(root.data);
        if(root.left !=null){
            System.out.print("left:");
            printTree(root.left);
        }
        if(root.right !=null){
            System.out.print("right:");
            printTree(root.right);
        }
    }

    //获取节点的高度
    public static int getHeight(Node p){
        return p == null ? 0 : p.height; // 空树的高度为0
    }

    /*
     * 右旋转
     * 右旋示意图(先把30的左节点指向25,然后再将节点20的右节点指向30,即对结点20进行右旋)
     *      30                       20
     *     /  \                     /  \
     *    20  40                  10   30
     *   /  \      --RR旋转-       /   /  \
     *  10   25                  5   25   40
     *  /
     * 5
     *
     */
    public Node RRRotate(Node p) {
        //记录下节点p的左子树,防止树断开
        Node node = p.left;     //失衡点的左子树的根结点20作为新的结点
        p.left = node.right;  //将新节点的右子树25成为失衡点30的左子树,等同于p.left = p.left.right
        node.right = p;         //将失衡点30作为新结点的右子树

        //重新设置失衡点30和新节点20的高度
        p.height = Math.max(getHeight(p.left), getHeight(p.right)) + 1;
        node.height = Math.max(getHeight(node.left), p.height) + 1;
        return node;
    }

    /*
     * LL旋转
     * 左旋示意图(先把20的右节点指向25,然后再将节点30的左节点指向20,即对结点30进行左旋))
     *      20                          30
     *     /  \                        /  \
     *    10  30                     20   40
     *       /  \      --LL旋转-     /  \   \
     *      25  40                 10  25  50
     *           \
     *           50
     *
     */
    public Node LLRotate(Node p){
        //记录下节点p的左子树,防止树断开
        Node node = p.right;     //失衡点的左子树的根结点20作为新的结点
        p.right = node.left;  //将新节点的右子树25成为失衡点30的左子树
        node.left = p;         //将失衡点30作为新结点的右子树

        //重新设置失衡点30和新节点20的高度
        p.height = Math.max(getHeight(p.left), getHeight(p.right)) + 1;
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
        return node;
    }

    // LR旋转
    public Node LRRotate(Node p){
        p.left = LLRotate(p.left); // 先将失衡点p的左子树进行RR旋转
        return RRRotate(p); // 再将失衡点p进行LL平衡旋转并返回新节点代替原失衡点p

    }
    // RL平衡旋转
    public Node RLRotate(Node p){
        p.right = RRRotate(p.right); // 先将失衡点p的右子树进行LL平衡旋转
        return LLRotate(p); // 再将失衡点p进行RR平衡旋转并返回新节点代替原失衡点p
    }

    public Node insert(Node root, int data) {
        //如果节点不存在,则直接创建新节点返回,新节点的高度默认为1
        if (root == null) {
            root = new Node(data);
            return root;
        }

        if (data < root.data) {
            //递归一直往下找,直到找到合适的位置为止
            root.left = insert(root.left, data);

            //如果树失衡,则进行平行操作
            if (getHeight(root.left) - getHeight(root.right) > 1) {
                /**
                 * 如果data等于root.left.data,那说明root之前没有左节点。
                 *
                 * 如果data小于root.left.data,那说明data是插入在root.left的子节点中,此时
                 * 我们还需要判断data具体是插入在root.left的左子节点还是右子节点。
                 *
                 * 1.如果是插入root.left的左子节,那么需要进行右旋转
                 * 2.如果是插入root.left的右子节点,那么需要先左旋转,然后再右右(即左右旋转)
                 */
                if (data < root.left.data) { //插入节点在失衡结点的左子树的左边
                    System.out.println("RR旋转");
                    root = RRRotate(root);
                }
                else { //插入节点在失衡结点的左子树的右边
                    System.out.println("左右旋转");
                    root = LRRotate(root);
                }
            }
        }
        else {
            root.right = insert(root.right, data);
            if (getHeight(root.right) - getHeight(root.left) > 1) {
                if(data <= root.right.data){//插入节点在失衡结点的右子树的左边
                    System.out.println("RL旋转");
                    root = RLRotate(root);
                }else{
                    System.out.println("LL旋转");//插入节点在失衡结点的右子树的右边
                    root = LLRotate(root);
                }
            }
        }

        //重新调整root节点的高度值
        root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
        return root;
    }

    public static void main(String[] args) {
        AVLTree tree = new AVLTree();
        Node root = null;

       /* root = tree.insert(root,30);
        root = tree.insert(root,20);
        root = tree.insert(root,40);
        root = tree.insert(root,10);
        root = tree.insert(root,25);*/
        //插入节点在失衡结点的左子树的左边, demo逐个放开验证
        //root = tree.insert(root,5);   //右旋 demo1
        //root = tree.insert(root,15);  //右旋 demo2
        //root = tree.insert(root,24);  //左右旋 demo3
        //root = tree.insert(root,26); //左右旋 demo4

        //测试左旋,右左旋
        root = tree.insert(root,20);
        root = tree.insert(root,10);
        root = tree.insert(root,30);
        root = tree.insert(root,25);
        root = tree.insert(root,40);
        //root = tree.insert(root,39);  //左旋 demo5
        //root = tree.insert(root,41);  //左旋  demo6
        //root = tree.insert(root,24);  //右左旋 demo7
        root = tree.insert(root,26);  //右左旋 demo8

        //打印树,按照先打印左子树,再打印右子树的方式
        tree.printTree(root);
    }
}

下面通过一张图片,总结一下分别在什么情况下需要左旋,左右旋,右旋,右左旋。 

左旋,右旋只需要一次旋转即可完成树的平衡

左右旋,右左旋需要2次旋转才能完成树的平衡

参考资料:

《大话数据结构》

https://blog.csdn.net/xiaojin21cen/article/details/97602146

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

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

相关文章

关键词(四)

关键词&#xff08;四&#xff09;一.具有争议的关键词—goto二.“空”的关键字—void1.void为什么不能定义变量2.void修饰函数返回值和参数3.void指针一.具有争议的关键词—goto goto语句非常灵活&#xff0c;其实就是从goto这个位置直接跳转到goto后面的那个数据&#xff08;…

单例模式、工厂模式

单例模式、一、单例模式1.1 饿汉模式1.1.1 实现1.1.2 补充1.2 懒汉模式1.2.1 单线程版1.2.2 多线程版二、工厂模式一、单例模式 单例模式是校招中最常考的设计模式之一。 啥是设计模式&#xff1f; 设计模式好比象棋中的"棋谱"&#xff1a;红方当头炮&#xff0c;黑…

软件测试人员究竟要掌握什么技能?顺便说下行业现状

最近团队内部产品在做性能测试中碰到一个问题&#xff0c;不仅仅这次性能测试&#xff0c;其实这在我这近10年工作过程中&#xff0c;经常碰到一些类似的事情&#xff0c;今天拿出来一件事说叨说叨。 1、事情经过 月中上线了一个功能&#xff0c;该功能会应对峰值流量&#x…

【安卓APP源码和设计报告(含PPT)——订餐系统

订餐系统实验报告 课程名称&#xff1a; Android程序设计 班 级&#xff1a; 学 号&#xff1a; 姓 名&#xff1a; 任课教师&#xff1a; 程序功能与环境&#xff08;服务器&#xff0c;手机实物照片&#xff0c;自己拍&#xff09; 程序功能 餐厅订餐系统服务器&#…

45. python %占位符格式化处理

45. %占位符格式化处理 文章目录45. %占位符格式化处理1.课题导入2. %占位符概念3. %d格式化为整数3.1 %d将整数格式化到指定位置3.2 %d将浮点数格式化为整数1. 知识回顾&#xff1a;用int函数将浮点数转换为整数2. 用%将浮点数格式化为整数4. %s格式化为字符串4.1 %s将整数格式…

论文笔记-时序预测-Triformer

论文标题&#xff1a; Triformer: Triangular, Variable-Specific Attentions for Long Sequence Multivariate Time Series Forecasting 论文链接&#xff1a; https://arxiv.org/abs/2204.13767v1 代码链接&#xff1a; https://github.com/razvanc92/triformer 摘要 各种现…

[附源码]计算机毕业设计基于JEE平台springbt技术的订餐系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Spring源码解析-环境变量

“不积跬步&#xff0c;无以至千里”。 今天聊一聊Spring中环境变量的动态添加和填充Bean属性的时候一些带“$”符号的属性值的解析问题。 因为最近做项目的时候发现了一个有意思的问题&#xff0c;之前也没关注过。因为项目中使用的容器类型是GenericXmlApplicationContext&a…

从GPT-3到CHAT-GPT(CHAT-GPT如何申请)

回顾2020年7月 2020年7月份有一个重大新闻&#xff0c;人工智能科研公司OpenAI&#xff0c;推出了它的新一代语言模型 GPT-3&#xff08;Generative Pretrained Transformer 3&#xff09;&#xff0c;这个事情在社交媒体影响甚广&#xff0c;甚至有一篇文章《一个新型 AI 震惊…

动态代理详解

目录 一、动态代理_代理模式简介 二、动态代理_JDK动态代理 dynamic 三、动态代理_CGLib动态代理 四、JDK和CGLib动态代理的区别 一、动态代理_代理模式简介 代理模式是23种设计模式之一。设计模式是前人总结的&#xff0c;在软件开发过程遇到常用问题的解决方案&#xff0…

微服务框架 SpringCloud微服务架构 微服务保护 30 初识Sentinel 30.4 引入cloud-demo

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 微服务保护 文章目录微服务框架微服务保护30 初识Sentinel30.4 引入cloud-demo30.4.1 引入cloud-demo30.4.2 微服务整合Sentinel30 初识Sent…

尝试使用CubeMX做stm32开发之十三:Clock Configuration(时钟树配置)

参考《STM32中文参考手册_V10》&#xff0c;研究CubeMX中有关时钟树配置。 一、系统时钟配置 三种不同的时钟源可被用于驱动系统时钟&#xff08;SYSCLK&#xff09;&#xff1a; HSI振荡器时钟HSE振荡器时钟PLL时钟 时钟源选择对应时钟配置寄存器&#xff08;RCC_CFGR&…

Android -- 每日一问:修改 SharedPreferences 后两种提交方式有什么区别?

知识点 SharedPreferences 类是一个接口类&#xff0c;真正的实现类是 SharedPreferencesImpl 。修改 SharedPreferences 需要获取它的 Editor&#xff0c;在对Editor进行put操作后&#xff0c;最后通过 commit 或者 apply 提交修改到内存和文件。当然有了两种都可以提交的方法…

Java进程线程介绍创建和执行销毁并理解线程安全和线程池 Native Method

目录1.进程和线程2.多线程的核心3.操作系统的多任务--以非常小的时间间隔交替执行4.native 修饰的方法5.Thread创建线程的两种方式1.普遍采用实现Runnable接口的方式2.继承Thread方式6.自定义线程用 new Thread(Runnable target) 启动源码分析6.1-new Thread(myThread)6.2对实例…

mysql8.0.21安装配置方法图文教程

记录了mysql 8.0.21 的安装配置方法&#xff0c;分享给大家。 一、下载 1、下载安装包 mysql下载路径 2、解压压缩包 3、在此目录下新建my.ini配置文件 [mysqld] # 设置 3306 端口 port3306 # 设置 mysql 的安装目录 basedirD:\mysql-8.0.21-winx64 # 设置 mysql 数据…

破案了!不会讲笑话不会作诗的chatGPT!

热出圈的chatGPT, 必须亲手试试热出圈的chatGPT, 必须亲手试试1 猜猜我是谁2 问网传图片李白风格注释代码3 写个程序看看4 帮我猜猜世界杯&#xff08;发了发了&#xff0c;偷笑脸&#xff09;5 知道李白吗&#xff1f;6 那你会写诗吗&#xff1f;6 那你讲脑经急转弯吗&#xf…

linux服务器安装docker(学习中)

linux服务器安装docker1、docker官网寻找官方文档1.1、卸载之前的docker1.2、安装yum工具类1.3、配置docker下载源的地址1.4、安装最新稳定版的docker1.5、启动docker1.6、docker镜像下载加速2、docker-卷-映射和挂载2.1、nginx1、docker官网寻找官方文档 然后根据官网文档进行…

高性能零售IT系统的建设08-9年来在互联网零售O2O行业抗黑产、薅羊毛实战记录及打法

前言 2012年左右转入互联网应用&#xff0c;由于本身在学校时就涉足过远程医疗影像中的DICOM安全领域这块&#xff0c;因此也是机缘巧合我进入互联网第一年就遇上了一次亿级的DDOS攻击以及千万级CC攻击短信系统的对抗。那时在公司一战成名&#xff0c;直接从team leader升到了主…

adb remount原理

1, 输入"fastboot flashing unlock" in adb ,waiting for the device 2, 输入"fastboot flashing unlock_critical"in adb ,waiting for the device 3, 输入"fastboot reboot" reboot the stb, press any key entering the boot mode 4, after…

tensorflow入门(三)tensorflow下神经网络参数的设置

参考 Tensorflow入门 - 云社区 - 腾讯云 神经网络中的参数是神经网络实现分类或回归问题中重要的部分。在tensorflow中&#xff0c;变量(tf.Variable)的作用就是保存和更新神经网络中的参数的。在tensorflow中&#xff0c;变量(tf.Variable)的作用就是保存和更新神经网络的参…