二叉搜索树之AVL树

news2024/12/25 1:45:01

AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺

序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种

解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过

1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

它的左右子树都是AVL树

左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

平衡因子bf = 右子树高度 - 左子树高度

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(log_2 n),搜索时间

复杂度O(log_2 n)

AVL树的插入

AVL树节点在定义时维护一个平衡因子,具体节点定义如下:

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;
        }
    }

AVL树的插入过程可以分为两步:

1. 按照二叉搜索树的方式插入新节点

2. 调整节点的平衡因子

按照二叉搜索树的方式插入新节点:

    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;
            }
        }

        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;
            }
        }

左单旋-插入位置在较高右子树的右侧:(parent.bf = 2, cur.bf = 1)

  //左单旋
    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 (parent == root){
            root = subR;
            root.parent = null;
        }else {
            if (pParent.left == parent){
                pParent.left = subR;
            }else {
                pParent.right = subR;
            }
            subR.parent = pParent;
        }
        subR.bf = 0;
        parent.bf = 0;
    }

右单旋-插入位置在较高左子树的左侧:(parent.bf = -2, cur.bf = -1)

//右单旋
    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;

        parent.left = subLR;
        subL.right = parent;
        if (subLR != null){
            subLR.parent = parent;
        }
        //必须先记录当前的父亲的父亲节点
        TreeNode pParent = parent.parent;
        parent.parent = subL;
        //检查当前节点是不是根节点
        if (parent == root){
            root = subL;
            root.parent = null;
        }else {
            //不是根节点,判断这颗子树是左子树还是右子树
            if (pParent.left == parent){
                pParent.left = subL;
            }else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
        subL.bf = 0;
        parent.bf = 0;
    }

左右双旋-插入位置在较高左子树的右侧:(parent.bf = -2, cur.bf = 1)

  //左右双旋
    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){
            subL.bf = 0;
            subLR.bf = 0;
            parent.bf = 1;
        }else if (bf == 1){
            subL.bf = -1;
            subLR.bf = 0;
            parent.bf = 0;
        }
    }

右左双旋-插入位置在较高右子树的左侧:(parent.bf = 2, cur.bf = -1)

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){
            subR.bf = 0;
            subRL.bf = 0;
            parent.bf = -1;
        }else if (bf == -1){
            subR.bf = 1;
            subRL.bf = 0;
            parent.bf = 0;
        }
    }

AVL树插入操作的完整代码+验证代码

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;
            }
        }

        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){
            subR.bf = 0;
            subRL.bf = 0;
            parent.bf = -1;
        }else if (bf == -1){
            subR.bf = 1;
            subRL.bf = 0;
            parent.bf = 0;
        }
    }

    //左右双旋
    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){
            subL.bf = 0;
            subLR.bf = 0;
            parent.bf = 1;
        }else if (bf == 1){
            subL.bf = -1;
            subLR.bf = 0;
            parent.bf = 0;
        }
    }

    //左单旋
    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 (parent == root){
            root = subR;
            root.parent = null;
        }else {
            if (pParent.left == parent){
                pParent.left = subR;
            }else {
                pParent.right = subR;
            }
            subR.parent = pParent;
        }
        subR.bf = 0;
        parent.bf = 0;
    }

    //右单旋
    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;

        parent.left = subLR;
        subL.right = parent;
        if (subLR != null){
            subLR.parent = parent;
        }
        //必须先记录当前的父亲的父亲节点
        TreeNode pParent = parent.parent;
        parent.parent = subL;
        //检查当前节点是不是根节点
        if (parent == root){
            root = subL;
            root.parent = null;
        }else {
            //不是根节点,判断这颗子树是左子树还是右子树
            if (pParent.left == parent){
                pParent.left = subL;
            }else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
        subL.bf = 0;
        parent.bf = 0;
    }
    //中序遍历
    public void inorder(TreeNode root){
        if (root == null)return;
        inorder(root.left);
        System.out.print(root.val + " ");
        inorder(root.right);
    }

    private int height(TreeNode root){
        if (root == null)return 0;
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);
        return leftHeight > rightHeight ? leftHeight+1 : rightHeight+1;
    }

    public boolean isBalanced(TreeNode root){
        if (root == null)return true;
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);

        if (rightHeight-leftHeight != root.bf){
            System.out.println("这个节点:"+root.val + "有异常!");
            return false;
        }

        return Math.abs(leftHeight-rightHeight) <= 1
                && isBalanced(root.left)
                && isBalanced(root.right);
    }
}

AVL树的删除

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不

同的是,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。

1、找到需要删除的节点

2、按照搜索树的删除规则删除节点--参考https://blog.csdn.net/crazy_xieyi/article/details/127627063

3、更新平衡因子,如果出现了不平衡,进行旋转。--单旋,双旋

AVL树的性能分析

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询

时高效的时间复杂度,即 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要

维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需

一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修

改,就不太适合。

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

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

相关文章

PCCW-HKT Futurera NFT 作品集来袭!

欢迎来到 Futurera&#xff0c;未来的虚拟城市&#xff01; 凭借庞大的 web2 资源&#xff0c;在全球首创的虚拟 5G 移动网络技术的鼎力支持下&#xff0c;Futurera 正力争跨越元宇宙的边界。 NFT 系列介绍 为庆祝 The Sandbox 中 Futurera 体验的开放&#xff0c;我们发布了一…

LSTM已死,Transformer当立(LSTM is dead. Long Live Transformers! ):下

2017 年,Google 在论文 Attention is All you need 中提出了 Transformer 模型,其使用 Self-Attention 结构取代了在 NLP 任务中常用的 RNN 网络结构。而且实验也证明Transformer 在效果上已经完败传统的 RNN 网络。Transformer 的整体模型架构如下图所示。尽管它看起来还是很…

python网络爬虫—快速入门(理论+实战)(七)

系列文章目录 &#xff08;1&#xff09;python网络爬虫—快速入门&#xff08;理论实战&#xff09;&#xff08;一&#xff09; &#xff08;2&#xff09;python网络爬虫—快速入门&#xff08;理论实战&#xff09;&#xff08;二&#xff09; &#xff08;3&#xff09; p…

平价款的血糖血压监测工具,用它养成健康生活习惯,dido F50S Pro上手

之前看有数据显示国内的三高人群越来越年轻&#xff0c;很多人不到三十就有了高血压、高血糖的问题&#xff0c;埋下了不小的健康隐患&#xff0c;加上前阵子的疫情管控放松&#xff0c;人们了解到了新冠病毒对心脏负担的认知&#xff0c;预防慢病被大众提上了日程&#xff0c;…

获取成员userID

文章目录一、简介二、获取token1、获取秘钥2、获取Token三、获取部门数据1、获取部门列表2、获取子部门ID列表3、获取单个部门详情四、获取成员信息1、读取成员2、获取部门成员3、获取部门成员详情一、简介 同步数据到企微&#xff1a; 企业如果需要从自有的系统同步通讯录到…

操作系统systemd启动自启服务进程

概念与背景 Systemd 是 Linux 系统工具&#xff0c;用来启动守护进程&#xff0c;已成为大多数发行版的标准配置。历史上&#xff0c;Linux 的启动一直采用init进程。在ubuntu18.04以后&#xff0c;都采用systemd启动。 更换主要原因是init进程有两个原因 启动时间长。init进…

Java高级-多线程

本篇讲解java多线程 基本概念&#xff1a; 程序、进程、线程 **程序(program)**是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码&#xff0c;静态对象。 **进程(process)**是程序的一次执行过程&#xff0c;或是正在运行的一个程序。是一个动态的过程…

12年老外贸的经验分享

回想这12年的经历&#xff0c;很庆幸自己的三观一直是正确的&#xff0c;就是买家第一不管什么原因&#xff0c;只要你想退货&#xff0c;我都可以接受退款。不能退给上级供应商&#xff0c;我就自己留着&#xff0c;就是为了避免因为这个拒收而失去买家。不管是什么质量原因&a…

2022年11月软考领证通知

纸质证书领取时间 根据往年各地软考证书的领取时间看&#xff0c;上半年软考证书领取一般在10月底陆续开始&#xff0c;下半年的证书领取时间一般在次年2/3月份左右开始&#xff08;各地证书领取具体时间不一样&#xff0c;届时请多留意当地证书领取通知。&#xff09; 1、证…

PyTorch学习笔记:nn.LeakyReLU——LeakyReLU激活函数

PyTorch学习笔记&#xff1a;nn.LeakyReLU——LeakyReLU激活函数 功能&#xff1a;逐元素对数据应用如下函数公式进行激活 LeakyReLU(x)max⁡(0,x)α∗min⁡(0,x)\text{LeakyReLU}(x)\max(0,x)\alpha*\min(0,x) LeakyReLU(x)max(0,x)α∗min(0,x) 或者 LeakyReLU(x){x,ifx≥0α…

在浏览器输入url到发起http请求,这过程发生了什么

当用户输入url&#xff0c;操作系统会将输入事件传递到浏览器中&#xff0c;在这过程中&#xff0c;浏览器可能会做一些预处理&#xff0c;比如 Chrome 会根据历史统计来预估所输入字符对应的网站&#xff0c;例如输入goog&#xff0c;根据之前的历史发现 90% 的概率会访问「ww…

1理想的大数据处理框架设计

以下内容基于极客 蔡元楠老师的《大规模数据处理实战》做的笔记哈。感兴趣的去极客看蔡老师的课程即可。 MapReduce 缺点 高昂的维护成本 因为mapreduce模型只有map和reduce两个步骤。所以在处理复杂的架构的时候&#xff0c;需要协调多个map任务和多个reduce任务。 例如计…

C#开发的OpenRA的扩展方法

C#开发的OpenRA的扩展方法 在我们以往的开发方法认知里, 对一个类进行扩展方法,只有继父类,然后在子类里创建新的内容。 但是C#又给我们上了一课,它不但可以采用前面的方法, 而且可以对类没有进行继承,也能扩展类型的方法。 这种方式,对于没有进行学习之前,看到代码就是…

Allegro更改线段,丝印,走线,形状,铜箔到不同层的方法

更改线段到不同的Class和Subclass的方法下面以更改线段为例进行讲解1、原先线段在Board Geometry→Soldermask_Top层2、选中线段&#xff0c;鼠标右击选择→Change class/subclass更改到所想要的Class和Subclass3、更改后的线段到Package Geometry→Silkscreen_Top层更改丝印&a…

详解shell中的运算符

目录 前言 一、运算指令 二、运算符号 练习 总结 前言 上一篇文章我们着重学习了 &#xff0c;shell中的执行流控制&#xff0c;本章我很学习和执行流控制相结合使用的运算符号与运算指令。 一、运算指令 计算的三种方式 (()) ##((a12)) let …

51单片机——74HC595的应用(SPI实践)

目录 SPI总线 SPI总线概述 SPI总线分类 SPI 优点及缺点 SPI接口硬件原理 SPI四种工作模式 74HC595应用 74HC595芯片概述 74HC595封装及管脚功能 74HC595工作原理 ​编辑 74HC595串行转并行点亮LED灯 程序实现 Proteus运行结构示意图 SPI总线 SPI总线概述 SPI&#…

【FiddlerScript】利用Fiddler中的FiddlerScript解除7K7K小游戏的防沉迷

本文仅供技术探讨&#xff0c;切勿用于非法用途案例网站:http://www.7k7k.com/准备的工具:配置好的Fiddler一个Fiddler官方英文版配置教程:https://www.bilibili.com/video/BV1rP4y1t7ZLFiddler中文版配重教程:https://www.bilibili.com/video/BV1CP4y1t7DR开始教程来到Fiddler…

10 个最难理解的 Python 概念

文章目录技术提升面向对象编程 (OOP)装饰器生成器多线程异常处理正则表达式异步/等待函数式编程元编程网络编程大家好&#xff0c;与其他编程语言相比&#xff0c;Python 是一门相对简单的编程语言&#xff0c;如果你想真正学透这门语言&#xff0c;其实可能并不容易。 今天我…

彻底弄懂HTTP缓存机制及原理(二)

强制缓存 从上文我们得知&#xff0c;强制缓存&#xff0c;在缓存数据未失效的情况下&#xff0c;可以直接使用缓存数据&#xff0c;那么浏览器是如何判断缓存数据是否失效呢&#xff1f; 我们知道&#xff0c;在没有缓存数据的时候&#xff0c;浏览器向服务器请求数据时&…

Linux(十一)生产者与消费者模型

引言 一、实现一个网关来过滤流经网关的数据 二、农忙时节收割麦子 生产者与消费者模型 模型实现 完整源码&#xff1a; 引言 阐述这个模型之前先引入俩个例子&#xff1a; 一、实现一个网关来过滤流经网关的数据 网关会捕捉大量的数据然后进行分析处理&#xff0c;之后…