二叉平衡树(左单旋,右单旋,左右双旋、右左双旋)

news2024/11/15 15:27:25

一、AVL树(二叉平衡树:高度平衡的二叉搜索树)

0、二叉平衡树

左右子树高度差不超过1的二叉搜索树。

public class AVLTree{
    static class AVLTreeNode {
        public TreeNode left = null; // 节点的左孩子
        public TreeNode right = null; // 节点的右孩子
        public TreeNode parent = null; // 节点的双亲
        public int val = 0;
        public int bf = 0; // 当前节点的平衡因子=右子树高度-左子树的高度
        
        public TreeNode(int val) {
            this.val = val;
        }
    }
    public TreeNode root;

    //插入函数等....
    
}
// 将AVLTreeNode定义为AVLTree的静态内部类

1、查找

二叉平衡树的查找和二叉搜索树的方法是一样的,因为它们具有相同的结构特点——右孩子val值小于根节点val,根节点val小于左孩子val。

2、插入

二叉搜索树的插入一定是插入到叶子节点的位置。
二叉平衡树将节点插入到叶子节点之后,要维护左右子树的平衡因此可能还要进行旋转操作。

  1. 先将数据插入到AVL树当中(和二叉搜索数一样)
  2. 插入进去后,根据平衡因子来进行对树的调整

3、插入后无需旋转的情况:

注意各节点bf值得变化

image.png

4、右单旋&左单旋:

对parent进行右单旋就是把parent.left提拔成根节点
注意各节点bf值得变化
image.png
对parent进行左单旋就是把parent.right提拔成根节点
注意各节点bf值得变化
image.png

补充:相对与根节点各个节点的名称,后续得图会用到这些名称

image.png

image.png
image.png

树parent是pParent(parent.parent)的一棵子树,对parent进行旋转后需要将新的根节点的parent指针指向pParent.

image.png
image.png

//检查 当前是不是就是根节点
if(parent == root) {
    root = subL;
    // subL.parent等于parent,subL提拔成了根节点,所以要将subL.parent设置为null,
    subL.parent = null;
}else {
    //不是根节点,判断这棵子树是左子树还是右子树
    if(pParent.left == parent) {
        pParent.left = subL;
    }else {
        pParent.right = subL;
    }
    subL.parent = pParent;
}

5、左右双旋 &右左双旋

  1. 注意指针指向
  2. 注意维护bf

右左双旋过程图:

image.png
image.png

左右双旋过程图:

image.png
image.png

下面这两幅图介绍了左右双旋时如何维护个节点的bf值(右左双选的不想画了,太累了)

image.png
image.png

/**
 * 左右双旋
 * @param parent
 */
private void rotateLR(TreeNode parent) {
    TreeNode subL = parent.left;
    TreeNode subLR = subL.right;
    int bf = subLR.bf;

    rotateLeft(parent.left);
    rotateRight(parent);

    // 这个规律很重要,中间状态的bf值不重要,根据初始状态的bf值来修改平衡状态的bf值
    if(bf == -1) {
        parent.bf = 1;
        subL.bf = 0;
        subLR.bf = 0;
    }else if(bf == 1){
        parent.bf = 0;
        subL.bf = -1;
        subLR.bf = 0;
    }
}
/**
 * 右左双旋
 * @param parent
 */
private void rotateRL(TreeNode parent) {
    TreeNode subR = parent.right;
    TreeNode subRL = subR.left;
    int bf = subRL.bf;
    
    rotateRight(parent.right);
    rotateLeft(parent);

    // 这个规律很重要,中间状态的bf值不重要,根据初始状态的bf值来修改平衡状态的bf值
    if(bf == 1) {
        parent.bf = -1;
        subR.bf = 0;
        subRL.bf = 0;
    }else if(bf == -1){
        parent.bf = 0;
        subR.bf = 1;
        subRL.bf = 0;
    }
}

6、总代码

package org.example;

/**
 * @Author 12629
 * @Description:
 */
public class AVLTree {
    static class TreeNode {
        public int val;
        public int bf;//平衡因子
        public TreeNode left;//左孩子的引用
        public TreeNode right;//右子树的引用
        public TreeNode parent;//父亲节点的引用

        public TreeNode(int val) {
            this.val = val;
        }
    }

    public TreeNode root;//根节点

    public boolean insert(int val) {
        TreeNode node = new TreeNode(val);
        if(root == null) {
            root = node;
            return true;
        }

        TreeNode parent = null;
        TreeNode cur = root;
        while (cur != null) {
            if(cur.val < val) {
                parent = cur;
                cur = cur.right;
            }else if(cur.val == val) {
                return false;
            }else {
                parent = cur;
                cur = cur.left;
            }
        }
        //cur == null
        if(parent.val < val) {
            parent.right = node;
        }else {
            parent.left = node;
        }
        //
        node.parent = parent;
        cur = node;
        // 平衡因子 的修改
        while (parent != null) {
            //先看cur是parent的左还是右  决定平衡因子是++还是--
            if(cur == parent.right) {
                //如果是右树,那么右树高度增加 平衡因子++
                parent.bf++;
            }else {
                //如果是左树,那么左树高度增加 平衡因子--
                parent.bf--;
            }

            //检查当前的平衡因子 是不是绝对值 1  0  -1
            if(parent.bf == 0) {
                //说明已经平衡了
                break;
            }else if(parent.bf == 1 || parent.bf == -1) {
                //继续向上去修改平衡因子
                cur = parent;
                parent = cur.parent;
            }else {
                //右树高-》需要降低右树的高度
                if(parent.bf == 2) {
                    if(cur.bf == 1) {
                        //左旋
                        rotateLeft(parent);
                    }else {
                        //cur.bf == -1
                        rotateRL(parent);
                    }
                }else {
                    //parent.bf == -2 左树高-》需要降低左树的高度
                    if(cur.bf == -1) {
                        //右旋
                        rotateRight(parent);
                    }else {
                        //cur.bf == 1
                        rotateLR(parent);
                    }
                }
                //上述代码走完就平衡了
                break;
            }
        }
        return true;
    }

    private void rotateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;

        rotateRight(parent.right);
        rotateLeft(parent);

        if(bf == 1) {
            parent.bf = -1;
            subR.bf = 0;
            subRL.bf = 0;
        }else if(bf == -1){
            parent.bf = 0;
            subR.bf = 1;
            subRL.bf = 0;
        }
    }

    /**
     * 左右双旋
     * @param parent
     */
    private void rotateLR(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;

        rotateLeft(parent.left);
        rotateRight(parent);

        if(bf == -1) {
            parent.bf = 1;
            subL.bf = 0;
            subLR.bf = 0;
        }else if(bf == 1){
            parent.bf = 0;
            subL.bf = -1;
            subLR.bf = 0;
        }
    }

    /**
     * 左单旋
     * @param parent
     */
    private void rotateLeft(TreeNode parent) {

        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;

        parent.right = subRL;

        subR.left = parent;
        if(subRL != null) {
            subRL.parent = parent;
        }

        TreeNode pParent = parent.parent;
        parent.parent = subR;

        if(root == parent) {
            root = subR;
            root.parent = null;
        }else {
            if(pParent.left == parent) {
                pParent.left = subR;
            }else {
                pParent.right = subR;
            }
            subR.parent = pParent;
        }
        subR.bf = parent.bf = 0;
    }

    /**
     * 右单旋
     * @param parent
     */
    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        parent.left = subLR;
        subL.right = parent;
        //没有subLR
        if(subLR != null) {
            subLR.parent = parent;
        }
        //必须先记录
        TreeNode pParent = parent.parent;
        parent.parent = subL;
        //检查 当前是不是就是根节点
        if(parent == root) {
            root = subL;
            // subL.parent等于parent,subL提拔成了根节点,所以要将subL.parent设置为null.
            subL.parent = null;
        }else {
            //不是根节点,判断这棵子树是左子树还是右子树
            if(pParent.left == parent) {
                pParent.left = subL;
            }else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
        subL.bf = 0;
        parent.bf = 0;
    }
    //中序遍历的结果是有序的 就能说明当前树 一定是AVL树吗?  不一定的
    private boolean inorder(TreeNode root){
        return inorderHelper(root,Long.MIN_VALUE);
    }
    private boolean inorderHelper(TreeNode root,long pre) {
        if(root == null) {
            return true;
        }
        if(!inorderHelper(root.left,pre)) {
            return false;
        }
        if(pre < root.val){
            pre = root.val;
            if(!inorderHelper(root.right,pre)){
                return false;
            }
            return true;
        }
        return false;
    }

    private int height(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftH = height(root.left);
        int rightH = height(root.right);

        return leftH > rightH ? leftH+1 : rightH+1;
    }

    public boolean isBalanced(TreeNode root) {
        if(root == null) {
            return true;
        }
        int leftH = height(root.left);
        int rightH = height(root.right);

        if(rightH-leftH != root.bf) {
            System.out.println("这个节点:"+root.val+" 平衡因子异常");
            return false;
        }

        return Math.abs(leftH-rightH) <= 1
                && isBalanced(root.left)
                && isBalanced(root.right)
                &&inorder(root);// 不仅要左右平衡,还要排序
    }
}

二叉平衡树的适用和不适用场景如下:
适用场景:

  1. 动态数据集合:
    • 当需要频繁地对数据集合进行插入、删除和查找操作时,二叉平衡树是一个很好的选择。它能够保持O(log n)的时间复杂度,避免了普通二叉搜索树在极端情况下退化为链表的问题。
  2. 需要保持数据有序性:
    • 二叉平衡树能够维护数据的有序性,同时也具有较高的查找效率。这在需要保持数据有序性并进行快速查找的场景中非常适用,例如索引数据库、缓存系统等。
  3. 需要高效的范围查询:
    • 由于二叉平衡树能够维护数据的有序性,因此可以很高效地进行范围查询,例如查找某个区间内的所有元素。这在一些需要范围查询的应用中很有用,如地理信息系统、网络路由表管理等。

不适用场景:

  1. 数据集合变化较小:
    • 如果数据集合的变化(插入、删除)很少,使用普通的二叉搜索树可能更加简单高效,因为不需要维护平衡性。
  2. 内存使用要求苛刻:
    • 二叉平衡树需要存储额外的平衡信息(如高度、平衡因子),会占用更多的内存。如果内存使用非常受限,可能需要选择其他更简单的数据结构。
  3. 对写操作要求极高:
    • 由于需要进行平衡操作,二叉平衡树的写操作(插入和删除)会稍微慢于普通的二叉搜索树。如果对写操作的性能要求极高,可能需要考虑其他数据结构。

总的来说,二叉平衡树是一种非常实用的数据结构,在需要高效管理动态有序数据集合的场景中表现优秀。但在某些特定的应用需求中,可能需要根据具体情况来权衡选择适合的数据结构。

** 画图不易,求赞。有不理解的地方可以私信我,必回复! **

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

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

相关文章

MySQL之基本查询(上)-表的增删查改

目录 Create(创建) 案例建表 插入 单行数据 指定列插入 单行数据 全列插入 多行数据 全列插入 插入是否更新 插入时更新 替换 Retrieve(读取) 建表插入 select列 全列查询 指定列查询 查询字段为表达式 为查询结果指定别名 结果去重 where条件 比较运算符 逻辑运…

【UE5.1 角色练习】16-枪械射击——瞄准

目录 效果 步骤 一、瞄准时拉近摄像机位置 二、瞄准偏移 三、向指定方向射击 四、连发 效果 步骤 一、瞄准时拉近摄像机位置 打开角色蓝图&#xff0c;在事件图表中添加如下节点&#xff0c;当进入射击状态时设置目标臂长度为300&#xff0c;从而拉近视角。 但是这样切…

数学建模中常用的数据处理方法

常用的数据处理方法 本文参考 B站西电数模协会的讲解视频 &#xff0c;只作笔记提纲&#xff0c;想要详细学习具体内容请观看 up 的学习视频。国赛的 C 题一般数据量比较大。 这里介绍以下两种方法&#xff1a; 数据预处理方法 数据分析方法 数据预处理方法 1. 数据清洗 为…

【后端开发实习】用MongoDB实现仓库管理的出库入库实战

用MongoDB实现仓库管理的出库入库 MongoDB什么是MongoDBMongoDB安装以及开始运行配置启动以及mongoshmongodb的基础使用命令启动和使用MongoDB服务数据库操作集合操作文档操作 项目部署在数据库中创建一张商品信息表提供信息表的增删改查操作接口 MongoDB 什么是MongoDB Mong…

德语中含“Augen”的惯用语表达-柯桥小语种学习德语考级

在我们的德语学习过程中&#xff0c;除了词汇的记忆&#xff0c;另一项重要的记忆任务就是惯用语的背诵啦。要知道&#xff0c;德语中有大量的Redewendung&#xff0c;他们以其言简意赅的表达&#xff0c;在日常用语中备受青睐。上一期我们已经学习了部分含有“Hand”的惯用语&…

每日一题~abc356(对于一串连续数字 找规律)

添加链接描述 题意&#xff1a;对于给定的n,m 。计算0~n 每一个数和m & 之后&#xff0c;得到的数 的二进制中 1的个数的和。 一位一位的算。最多是60位。 我们只需要计算 在 1-n这些数上&#xff0c;有多少个数 第i位 为1. 因为是连续的自然数&#xff0c;每一位上1 的…

ARM学习(29)NXP 双coreMCU IMX1160学习----NorFlash 启动引脚选择

ARM学习&#xff08;28&#xff09;NXP 双coreMCU IMX1160学习----NorFlash 启动引脚选择 1、多种启动方式介绍 IMX1166 支持多组flexSPI 引脚启动&#xff0c;FlexSPI1以及FlexSPI2&#xff0c;通过boot cfg可以切换FlexSPI得实例。 每个实例又支持多组引脚&#xff0c;总共…

Let‘s Encrypt性价比最高的申请SSL证书

SSL/TLS证书作为确保网站数据传输安全性的重要手段&#xff0c;受到了广大网站运营者的青睐。然而&#xff0c;高昂的证书费用往往成为许多小型网站和个人博客的负担。 申请Lets Encrypt免费泛域名SSL证书步骤 1. 登录来此加密网站&#xff0c;输入域名&#xff0c;可以勾选泛…

食物链之带权并查集解法

直接看题&#xff1a;https://www.acwing.com/problem/content/description/242/ 下面就是代码的实现了&#xff0c;因为自己与自己肯定是同类我们初始化为0. 下面是AC代码&#xff1a; #include<bits/stdc.h> using namespace std; int n,k; int fk,x,y; int fa[10001…

染色法判定二分图

什么是二分图&#xff1f; 二分图&#xff0c;也称作二部图&#xff0c;是图论中的一种特殊模型。在一个无向图G(V,E) 中&#xff0c;如果顶点集合 V 可以被分割成两个互不相交的子集 A 和 B&#xff0c;并且图中的每条边 (i,j) 关联的两个顶点 i 和 j 分别属于这两个不同的顶…

第三课网关作用

实验拓扑图&#xff1a; 基础配置&#xff1a; PC1的基础配置 PC2的基础配置&#xff1a; PC4的基础配置 AR1添加PC4网段: 并且添加pc1,pc2的网段。 并且添加pc1,pc2的网段。 原理&#xff1a;PC4先把数据交给100.100.100.1&#xff0c;交给了路由器&#xff0c;路由器再把数…

分贝通差旅管理费控BI 助力企业差旅报销降本

在企业的日常运营中&#xff0c;每张机票、每个酒店预订、每次用车、每笔对公付款&#xff0c;这些看似零碎的支出累积起来&#xff0c;每月往往能轻松达到数百万元。面对如此庞大的支出&#xff0c;如何及时发现并控制不合理支出&#xff0c;成为企业成本控制的关键。为此&…

引用计数器(kref)

1、什么是引用计数器 如果我们写了一个字符驱动&#xff0c;当硬件设备插上时&#xff0c;系统会生成一个设备节点。用户在应用空间操作这个设备节点就可以操作设备。如果此时将硬件断开&#xff0c;驱动是不是就要立刻释放呢&#xff1f;如果立刻释放&#xff0c;应用程序是不…

计算机网络体系结构解析

OSI参考模型 与 TCP/IP模型 如图所示 TCP/IP模型有几层 应用层&#xff1a;只需要专注于为用户提供应用功能 HTTP、SMTP、Telnet等&#xff0c;工作在操作系统中的用户态&#xff0c;传输层及以下工作在内核态传输层&#xff1a;为应用层提供网络支持&#xff08;TCP、UDP传…

《昇思25天学习打卡营第01天|qingyun201003》

打卡 日期 心得 我的主语言并不是Python,以及现在从事的工作也并不是开发&#xff1b;所以对于这个系列的课程&#xff0c;学习起来是较为困难的&#xff0c;所以基于这种情况&#xff0c;该如何进行学习&#xff1f;我的做法是全部交给AI&#xff0c;使用AI一步步解析代码&a…

xcode配置swift使用自定义主题颜色或者使用RGB或者HEX颜色

要想在xcode中使用自定义颜色或者配置主题色&#xff0c;需要在Assets中配置&#xff0c;打开Assets文件&#xff0c;然后点击添加Color Set&#xff1a; 输入颜色的名称&#xff0c;然后选中这个颜色&#xff0c;会出现两个颜色&#xff1a; Any Appearance表示亮色模式下使用…

JRT打印药敏报告

最近没写jrt系列博客&#xff0c;不是中途而废了。而是在写微生物系统。今天终于把微生物大体完成了&#xff0c;伴随着业务的实现&#xff0c;框架趋于完善和稳定。构建一套完美而强大的打印体系一直是我的理想&#xff0c;从最开始C#的winform打印控件到刚接触bs时候用js打印…

react学习——24redux实现求和案例(精简版)

1、目录结构 2、count/index.js import React, {Component} from "react"; //引入store,用于获取数据 import store from ../../redux/store export default class Count extends Component {state {count:store.getState()}componentDidMount() {//监测redux中的…

手撸俄罗斯方块(一)——简单介绍

手撸俄罗斯方块 简单介绍 《俄罗斯方块》&#xff08;俄语&#xff1a;Тетрис&#xff0c;英语&#xff1a;Tetris&#xff09;&#xff0c;是1980年末期至1990年代初期风靡全世界的电脑游戏&#xff0c;是落下型益智游戏的始祖&#xff0c;电子游戏领域的代表作之一&a…

关于气象探测设备的介绍

气象探测设备概述 气象探测设备是用于收集、记录和分析大气环境信息的专用工具。它们能够实时监测气温、湿度、气压、风速、风向、降雨量等多种气象要素&#xff0c;为天气预报、气候研究、农业生产和环境保护等领域提供重要数据支持。气象探测设备种类繁多&#xff0c;包括地…