数据结构之AVL树的 “奥秘“

news2024/11/18 1:39:47

二叉树查询性能分析:

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索在二叉搜索树树平均查找长度是结点的深度的函数,即结点越深,则比较次数越多

如图:

 

下面就是对二叉搜索树的改进AVL树

目录:

一.AVL树的概念

二.AVL树的实现

三.AVL树的验证

四.AVL树的删除(了解)

五.AVL树的性能分析

一. AVL树的概念:

1. 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺 序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-VelskiiE.M.Landis在1962年 发明了一种 解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过 1(需要对树中的结点旋转),即可降低树的高度,从而减少平均搜索长

 

2. 这个我们要定义一个平衡因子,平衡因子 = 右树高度 - 左树高度。

3. 如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 搜索时间复杂 度(Log2^N)。




二 AVL树的实现:

1. 为了AVL树实现简单,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;

 2.AVL树的插入:

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

2.2. 调整节点的平衡因子

根据平衡因子可以分为几种情况:

情况一:Parent的平衡因子为 0,已经平衡无需调整;

情况二:Parent的平衡因子为 1,-1,继续向上调整;

情况三:Parent的平衡因子为 2,cur的平衡因子为,左单旋(同号)

情况四:Parent的平衡因子为 2,cur的平衡因子为-1右左双旋(异号)

情况五:Parent的平衡因子为 -2,cur的平衡因子为-1右单旋(同号)

情况六:Parent的平衡因子为 -2,cur的平衡因子为 1 左右双旋(异号)

代码:

  //插入:
    public boolean insert(int val) {
        TreeNode node = new TreeNode(val);

        if(root == null){
           root = node;
           return true;
        }

        TreeNode cur = root;
        TreeNode parent = null;
        while (cur != null){
            if(cur.val < val){
                parent = cur;
                cur = cur.right;
            }else if(cur.val > val){
                parent = cur;
                cur = cur.left;
            }else {
                return false;
            }
        }

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

            //检查当前的平衡因子是不是0,1,-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;
    }

3.AVL树的旋转:

3.1. 新节点插入较高左子树的左侧---右单旋 :

Parent的平衡因子为 -2,cur的平衡因子为-1右单旋(同号)

图解:

 代码:

 /**
     * 右单旋
     * @param parent
     */
    private void rotateRight(TreeNode parent){
        TreeNode subL = parent.left;
        TreeNode subRL = subL.right;

        parent.left = subRL;
        subL.right = parent;
        //如果旋转的整棵树也是一个子树,记录下原来该树的父亲,后续修改
        TreeNode pParent = parent.parent;

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

        //看看整棵树是否也是一个子树
        if(parent == root){
            subL = root;
            root.parent = null;
        }else {
            //是子树就确定这棵树是左子树还是右子树
            if(pParent.left == parent){
                pParent.left = subL;
            }else {
                pParent.right = subL;
            }
        }

        subL.parent = pParent;

        //修改平衡因子
        subL.bf = 0;
        parent.bf = 0;
    }

3.2. 新节点插入较高右子树的右侧---左单旋:

Parent的平衡因子为 2,cur的平衡因子为,左单旋(同号)

图解:

代码:

/**
     * 左单旋
     * @param parent
     */
    private void rotateLeft(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;

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

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

        //看看整棵树是否也是一个子树
        if(parent == root){
            subR = root;
            root.parent = null;
        }else {
            //是子树就确定这棵树是左子树还是右子树
            if(pParent.left == parent){
                pParent.left = subR;
            }else {
                pParent.right = subR;
            }
        }
        subR.parent = pParent;

        parent.bf = 0;
        subR.bf = 0;
    }

3.3. Parent的平衡因子为 -2,cur的平衡因子为 1 左右双旋(异号):

图解:subLR的平衡因子区分情况

情况一:subLR平衡因子等于-1

情况二:subLR平衡因子等于1

 代码:

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

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


        //双旋时subLR的值-1,1,会有2种插入方式
        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;
        }
    }

4. Parent的平衡因子为 2,cur的平衡因子为-1右左双旋(异号):

图解:

情况一:subRL平衡因子等于1

情况二:subRL平衡因子等于-1

代码: 

  /**
     *右左双旋:
     * @param parent
     */
    private void rotateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;

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


        //双旋时subRL的值-1,1,会有2种插入方式
        if(bf == 1){
            parent.bf = -1;
            subRL.bf = 0;
            subR.bf = 0;
        }else if(bf == -1){
            parent.bf = 0;
            subRL.bf = 0;
            subR.bf = 1;
        }
    }



三.AVL树的验证:

分两步:

1. 验证其为二叉搜索树:

代码:

//中序遍历:
    public void inorder(TreeNode root){
        if(root == null){
            return;
        }

        inorder(root.left);
        System.out.println(root.val);
        inorder(root.right);
    }

    //求高度
    private int height(TreeNode root){
        if(root == null){
            return 0;
        }

        int leftH = height(root.left);
        int rightH = height(root.right);

        return Math.max(leftH,rightH) + 1;
    }

2. 验证其为平衡树: 

 //判断树是否为平衡二叉树
    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) < 2
                && isBalanced(root.left)
                && isBalanced(root.right);
    }


四.AVL树的删除(了解):

1、找到需要删除的节点

2、按照搜索树的删除规则删除节点--参考Map和Set及哈希--的奥秘(详解)-CSDN博客中二叉搜索树的删除

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



五.AVL树性能分析:

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询 时高效的时间复杂度,即 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要 维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要 一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修 改,就不太适合

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

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

相关文章

继电器的工作原理及作用

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 前言1.基本概念3.主要作用4.基本结构5.工作原理 前言 送给大学毕业后找不到奋斗方向的你&#xff08;每周不定时更新&…

联合贷款系统架构与流程解析

在联合贷款作为一种创新的融资模式&#xff0c;正逐渐受到越来越多金融机构和借款人的青睐。本文将分析联合贷款产品的优势&#xff0c;详细描述其流程&#xff0c;并结合实际案例展示联合贷款在实际应用中的场景。帮助读者增进对于联合贷款系统架构及其运作机制的了解。 一、…

600条最强 Linux 命令总结(非常详细)零基础入门到精通,收藏这一篇就够了

一、基本命令 uname -m 显示机器的处理器架构 uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 (SMBIOS / DMI) hdparm -i /dev/hda 罗列一个磁盘的架构特性 hdparm -tT /dev/sda 在磁盘上执行测试性读取操作系统信息 arch 显示机器的处理器架构 uname -…

UE5 UMG UI编辑器工作流

创建UI控件 1.在内容菜单&#xff08;Content Browser&#xff09;面板&#xff0c;点击添加&#xff08;Add&#xff09;或者右键空白处&#xff0c;依次选择用户界面&#xff08;User Interface&#xff09;/ 控件蓝图&#xff08;Widget Blueprint&#xff09;。 2.在弹出…

领域驱动模型设计与微服务架构落地(四)之DDD分层架构设计

那么聊完领域模型之后,其实我们会发现,接下来,很多的程序员可能就会直接上代码,因为很多的程序员觉得这个你的战略设计跟我们落地的代码没有关系。哪怕你可能说得天花乱坠,可是做为底层的开发人员,我只关心手头上的功能有没有实现,实现完成之后有没有BUG。 那么我们该如…

全网最详细的自动化测试

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 软件测试作为软件生命周期中不可缺少的组成部分&#xff0c;对提高软件质量起着重要作用。随着软件测试的发展&#xff0c;自动化测试技术也得到了很大提高。 …

CART算法:决策树的双面剑

一 引言 上一篇文章 决策树算法&#xff1a;ID3与C4.5的对比分析 中介绍了ID3和C4.5两种决策树算法&#xff0c;这两种决策树都只能用于分类问题&#xff0c;而CART&#xff08;classification and regression tree&#xff09;决策树算法它可以处理分类问题&#xff08;Class…

修复数据库中的 “Access Denied: SUPER Privilege Required” 错误

当您使用数据库时&#xff0c;您可能会看到错误消息&#xff1a;“Access denied; you need (at least one of) the SUPER privilege(s) for this operation”。当您的数据库用户没有足够的权限来执行某些操作时&#xff0c;就会发生这种情况。 本文中&#xff0c;我们将查看导…

SQL手工注入漏洞测试(MongoDB数据库)靶场通关攻略

构造数据回显 });return ({title:1,content:2 成功回显1,2&#xff0c;接下来我们开始尝试查询数据库 });return({title:tojson(db),content:2 得到之后我们就可以继续查询他的表名了 });return({title:tojson(db.getCollectionNames()),content:2 最后我们就可以爆出他表里的数…

【EI会议截稿通知】第六届光电科学与材料学术会议 (ICOSM 2024)

第六届光电科学与材料学术会议 (ICOSM 2024) 2024 6th Conference on Optoelectronic Science and Materials 重要通知 重要通知&#xff1a;经组委会商议决定&#xff0c;第六届光电科学与材料学术会议 (ICOSM 2024) 将于2024年9月7日线上召开&#xff0c;具体议程及线上参…

20L水箱植保无人机技术详解

1. 性能与载重 高效作业能力 本款20L水箱植保无人机专为大面积农田作业设计&#xff0c;具备出色的性能与载重能力。其最大载重量可达20kg&#xff0c;不仅轻松搭载20L的水箱及药液&#xff0c;还能根据实际作业需求配置额外的传感器、摄像头等设备&#xff0c;实现多功能集成…

string类题目(上)

string类题目 题目来源&#xff08;Leetcode&#xff09; 题目一&#xff1a;仅仅反转字母 分析 这个反转的特点在于只反转字母&#xff0c;不反转特殊字符。 法一&#xff1a;如果我们让一个正向迭代器指向第一个字符&#xff0c;让一个反向迭代器指向最后一个字符&#xf…

如何使用C4D云渲染服务打开图片渲染器窗口?

C4D以其对第三方渲染器的广泛支持而闻名&#xff0c;能够创造出高质量的视觉作品。这些渲染效果涵盖了逼真的光照和阴影效果、真实的材质质感、精细入微的图像细节&#xff0c;以及令人印象深刻的快速渲染能力。C4D云渲染功能进一步增强了其性能&#xff0c;用户可以通过一个统…

Win10用户必备!三款超实用第三方录屏软件大推荐

大家好&#xff01;今天我要和大家分享一下Win10的录屏操作以及使用体验&#xff0c;并且还会推荐几款好用的录屏工具&#xff0c;希望对大家有所帮助。 Win10录屏操作以及使用体验&#xff1a; Win10自带的录屏主要是为游戏录制而开发的&#xff0c;系统自带不需要额外下载客…

拍立淘API返回值:商品搜索与广告推广的完美结合

拍立淘&#xff08;一种基于图像搜索的购物功能&#xff0c;常见于淘宝等电商平台&#xff09;的API&#xff08;应用程序接口&#xff09;返回值在商品搜索与广告推广的结合中扮演了关键角色。这种结合不仅提升了用户体验&#xff0c;还通过精准推荐和广告展示增加了商家的曝光…

DDIA 分布式数据的分区与复制 - 基于 Redis、Kafka、Elasticsearch 的深入分析

引言 本文基于《Designing Data-Intensive Applications》一书&#xff08;设计数据密集型应用&#xff0c;简称 DDIA&#xff09;&#xff0c;深入探讨了 Redis、Kafka 和 Elasticsearch 等常用组件的分区与复制机制。通过这些案例分析&#xff0c;我们可以更好地理解分布式系…

python-竞赛技巧(赛氪OJ)

[题目描述] 在 ACM 竞赛中&#xff0c;当遇到有两个队伍&#xff08;人&#xff09; 解出相同的题目数量的时候&#xff0c;我们需要通过他们解决问题的总时间进行排序。 一共有 N 条时间被以时( Hours )&#xff0c; 分( Minutes )&#xff0c;秒( Seconds )的形式记录。 你必…

​北斗终端:无人驾驶领域的导航新星

一、北斗终端在无人驾驶领域的应用 北斗终端&#xff0c;作为我国自主研发的北斗卫星导航系统的重要组成部分&#xff0c;其在无人驾驶领域中的应用正逐步显现其独特魅力。北斗系统的高精度、高可靠性和良好的抗干扰性能&#xff0c;为无人驾驶车辆提供了精确的定位和导航服务…

生信圆桌x 生信人论坛:生物信息学爱好者的交流与学习社区

介绍 生信人论坛是一个专为生物信息学&#xff08;生信&#xff09;领域的研究人员、学生和爱好者创建的在线社区。在这里&#xff0c;用户可以分享他们的研究经验、讨论最新的生信技术和工具&#xff0c;并向同行请教各种生信分析问题。生信人论坛不仅是一个知识分享的平台&a…

云朵备份:微信的云备份工具

什么是 云朵备份 &#xff1f; 云朵备份 是一个微信云备份程序&#xff0c;使用云朵备份可以将微信数据备份到服务器&#xff0c;通过浏览器访问数据&#xff0c;你可以像正常使用微信一样浏览数据和搜索数据&#xff08;参考微信网页版&#xff09;&#xff0c;除了不能发消息…