一篇文章带你轻松手撕AVL树的构建

news2025/1/11 7:43:43

1.AVL树介绍

我们知道一般情况下二叉搜索树的查找效率是很高的,但是遇到极端情况下时间复杂度就会来到O(N)

4b4f6542c0d84118ac42150a45982b15.png

 那么为了消除这种极端情况的影响,我们就需要调节这个二叉树通过一些操作转成一颗二叉平衡树,调节完毕就会得到一颗AVL树。

2.AVL树模拟实现

(0)结点结构

static class Node{
       public int val;//结点值
       public Node left;//左孩子
       public Node right;//右孩子
       public Node parent;//父亲结点
       public int bf;//平衡因子
}

这里和一般的二叉树最大的差别就是多一个平衡因子bf,这个是方便我们调节二叉树高度的,因为作为一颗平衡树要保证所有结点的左右子树的高度差的绝对值要小于等于1。

那么接下来我们来一步一步构建一颗AVL树,那么一般情况下和二叉搜素树的构建没有区别,只不过当出现有的结点不满足红色字体的条件时,就需要进行旋转调节高度。

(1)右单旋

当我们按照5 6 3 4 2 1的顺序构建AVL树时,当插入1结点时,就会出现5结点的bf==-2,此时就要进行右单旋,以拔高5结点右树的高度。

ba7d729027754ea68d7b28af0ee100a3.png

 

 

 这里可以发现,最后平衡因子发生改变的只有3和5结点,且平衡因子均变成了0。

上图转换为代码语言为:

当出现上图这种情况,即parent.bf == -2 且parent的左子树 parent.left.bf == -1时,我们进行右单旋

 

public static void rotateRight(Node parent){
        //图1
        Node subL=parent.left;
        Node subLR=subL.right;//这个值可能为null
        Node tmp=parent.parent;//记录parent的父亲结点-也可能为空 if parent == root 时
        if(parent==root){
            root=subL;//更新root
        }
        //图2 连接3 5结点
        subL.right=parent;
        parent.parent=subL;
        //图3--连接4 5结点
        parent.left=subLR;
        if(subLR!=null){
            subLR.parent=parent;
        }
        //parent可能为某个结点的子节点,需要进行连接
        subL.parent=tmp;
        if(tmp!=null){
            if(tmp.left==parent){
                tmp.left=subL;
            }else{
                tmp.right=subL;
            }
        }
        //图4--更新bf
        subL.bf=0;
        parent.bf=0;
    }

 

(2)左单旋

左单旋和右单旋十分甚至九分相似

当我们按 2 1 4 3 5 6 的顺序插入时,当插入到6结点时,会出现2结点的平衡因子 == 2 

这时候就要进行左单旋:

91259eb18d85407fa267a338138a5558.png

 

 

 这里可以发现,最后平衡因子发生改变的只有2和4结点,且平衡因子也均变成了0。

上图转换为代码语言为:

当出现上图这种情况,即parent.bf == 2 且parent的左子树 parent.left.bf == 1时,我们进行左单旋:

public static void rotateLeft(Node parent){
        //图1
        Node subR=parent.right;
        Node subRL=subR.left;//这个值可能为null
        Node tmp=parent.parent;//记录parent的父亲结点-也可能为空 if parent == root 时
        if(parent==root){
            root=subR;//更新root
        }
        //图2 连接2 4结点
        subR.left=parent;
        parent.parent=subR;
        //图3--连接2 3结点
        parent.right=subRL;
        if(subRL!=null){
            subRL.parent=parent;
        }
        //parent可能为某个结点的子节点,需要进行连接
        subR.parent=tmp;
        if(tmp!=null){
            if(tmp.left==parent){
                tmp.left=subR;
            }else{
                tmp.right=subR;
            }
        }
        //图4--更新bf
        subR.bf=0;
        parent.bf=0;
    }

 

(3)右左双旋

有的时候,当插入出现问题是不是一次简单的左/右单旋就能解决问题,这时候就涉及到双旋。

例如:按 2 1 5 4 6 3进行插入时,当插入3结点时,我们会得到这样的图

 此时我们直接左单旋会得到:

d05911b707bb4d00bdb23d5ef168b4f3.png

 

可以发现这样调节是没有用的,所以需要采用右左双旋的办法,先对subR这颗子树进行右单旋,再对整体进行左单旋

 7a1ba189a1584dcca4a667ec33bdfd9e.png那么这里平衡因子的更新并不是固定的,是需要分情况的:

 

现在插入的是3,bf的更新为 parent.bf=0,subR.bf=0

如果插入的是4.5 bf的更新为 parent.bf=-1,subR.bf=0

6bfd4265472646248ee95fb19a006da6.png

 

 由此我们可以发现parent.bf值的决定性因素为:新插入结点的父节点的平衡因子大小

-------------------------------------------------------------------------------------------------------------------------

下图中:

如果subRL.bf == -1,则 旋转完后,subRL.bf=0;subR.bf=1;parent.bf=0;

如果subRL.bf == 1,则 旋转完后,subRL.bf=0;subR.bf=0;parent.bf=-1;

922156e986954d96b5f8c4fc8cee2c2b.png

当出现上图这种情况,即parent.bf == 2 且parent的左子树 parent.left.bf == -1时,我们进行右左单旋。

右左双旋代码实现:

 

public static void rotateRL(Node parent){
        Node subR = parent.right;
        Node subRL = subR.left;
        int bf= subR.bf;
        //先对右子树进行右单旋
        rotateRight(parent.right);
        //再对整体进行左单选
        rotateLeft(parent);
        //更新bf
        if(bf==1){
            parent.bf=-1;
            subR.bf=0;
        }else if(bf==-1){
            parent.bf=0;
            subR.bf=1;
        }
        subRL.bf=0;
    }

(4)左右双旋

做法与右左双旋一致,但也要分情况更新平衡因子

按5 6 2 1 4 3的顺序,插入3结点的时候,不能直接整体右旋,需要先局部左旋再整体右旋。 

02a6acebecf64ed6b10b06e270014b74.png

根据subLR的bf不同,我们最后的bf调节也需要分情况讨论。

2abbbbd138084faab6b7a35460a47aae.png

 

在这里我直接给出结论:

subLR.bf == -1;旋转后,subL.bf = 0;subLR.bf=0;parent.bf=1;

subLR.bf == 1;旋转后,subL.bf = -1;subLR.bf=0;parent.bf=0;

代码如下:

public static void rotateLR(Node parent){
        Node subL=parent.left;
        Node subLR=subL.right;
        int bf= subLR.bf;
        //先对左子树进行左单旋
        rotateLeft(parent.right);
        //再对整体进行右单旋
        rotateRight(parent);
        //更新bf
        if(bf==1){
            parent.bf=0;
            subL.bf=-1;
        }else if(bf==-1){
            parent.bf=1;
            subL.bf=0;
        }
            subLR.bf=0;
    }

插入函数

public static void insertNode(int val){
        Node node=new Node(val);//创建新结点

        if(root==null){
            root=node;
        }else{
            //先按照二叉搜索树的方式插入
            Node cur=root;
            Node prv=cur.parent;
            while(cur!=null){
                prv=cur;
                if(cur.val>node.val){
                    cur=cur.left;
                    if(cur==null){
                        prv.left=node;
                    }
                }else if(cur.val< node.val){
                    cur=cur.right;
                    if(cur==null){
                        prv.right=node;
                    }
                }else{
                    return;//相等不插入树中
                }
            }
            //设置新结点的父节点
            node.parent=prv;
            //更新所有的父节点bf平衡因子
            Node parent=node.parent;
            cur = node;
            while(parent!=null){
                if(cur==parent.left){
                    parent.bf-=1;
                }else{
                    parent.bf+=1;
                }
                //检查调节完的bf是否正确
                if(parent.bf==2){
                    if(cur.bf==1){
                        rotateLeft(parent);
                    }else if(cur.bf==-1){
                        rotateRL(parent);
                    }
                    break;//旋转完成之后,以parent为根的树已经和插入之前的高度相同,
                          //不会再对上层树的平衡性造成影响,毕竟parent.bf是0
                }else if(parent.bf==-2){
                    if(cur.bf==-1){
                        rotateRight(parent);
                    }else{
                        rotateLR(parent);
                    }
                    break;//旋转完成之后,以parent为根的树已经和插入之前的高度相同,
                          //不会再对上层树的平衡性造成影响
                }else if(parent.bf==0){
                    //bf==0 代表平衡了,不用向上调节了
                    break;
                }else{
                    cur=parent;
                    parent=parent.parent;
                }
            }
        }
    }

 

测试建立的AVL树是否正确(两个方面)

1.检查是否是二叉搜索树

通过中序遍历的方式来判断--判断得到的数组是不是有序的

代码如下:

//判断是不是二叉搜索树
    private static List<Integer> list=new ArrayList<>();
    private static void isSearch(Node root){
        //中序遍历判断是否有序
        inorder(root);
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i)+" ");
        }
        System.out.println();
    }

    private static void inorder(Node root){
        if(root==null)return;
        inorder(root.left);
        list.add(root.val);
        inorder(root.right);
    }

 

2.检查是否是二叉平衡树

通过判断所有结点的左右子树高度之差来判断

代码如下:

//判断是不是二叉平衡树
    public static int height(Node root){
        if(root==null)return 0;
        int leftLen=height(root.left);
        int rightLen=height(root.right);
        return leftLen>rightLen?leftLen+1:rightLen+1;
    }

    private static boolean isBalance(Node root){
        if(root==null)return true;
        int leftLen=height(root.left);
        int rightLen=height(root.right);
        //同时判断平衡因子是否正确
        if(rightLen-leftLen!=root.bf){
            System.out.println("这个结点"+root.val+"平衡因子异常");
            return false;
        }
        return Math.abs(leftLen-rightLen)<=1&&
        isBalance(root.left)&&isBalance(root.right);
    }

 

检测

结果:

public static boolean isAVL(Node root){
        isSearch(root);
        return isBalance(root);
    }

测试用例:

1:- 常规场景1
{16, 3, 7, 11, 9, 26, 18, 14, 15}

5ea455aaf7d24072959a65d25f04a18d.png

2.- 特殊场景2
{4, 2, 6, 1, 3, 5, 15, 7, 16, 14}

9dc955e327dc455eb45a273723bd5746.png

 

 

AVL树的适用场景

不难发现,需要构造一个AVL树如果输入极端的话,需要进行大量的旋转操作,这无疑是很消耗时间的,由此可以看出AVL树不适合大量插入和删除,适合的是场景应该满足插入少删除少

 

 

 

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

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

相关文章

【Linux】第二部分 保姆级手把手教你如何安装Linux

【Linux】第二部分 保姆级手把手教你如何安装Linux 文章目录【Linux】第二部分 保姆级手把手教你如何安装Linux2.保姆级手把手教你如何安装Linux首先下载vmware接下来下载centOS**接着开启虚拟机,对操作系统进行配置**总结2.保姆级手把手教你如何安装Linux 首先下载vmware vm…

阿里云服务器安装oracle11g

1.服务器配置 //linux版本 CentOS-7-x86_64 //oracle版本 linux.x64_11gR2 //查看服务器的CPU个数 cat /proc/cpuinfo | grep physical | sort -n | uniq | wc -l //查看服务器的型号 dmidecode -s system-product-name //查看服务器的cpu型号…

LeetCode题解 14 (3,98) 无重复字符的最长子串,验证二叉搜索树

文章目录无重复字符的最长子串(3)代码解答&#xff1a;验证二叉搜索树(98)代码解答&#xff1a;无重复字符的最长子串(3) 从题目中可以得知我们要找到该字符串中没有重复元素的最长字串,这道题可以采用滑动窗口的方法来解决,今天在这里我们采用新的方法来解决。 首先我们先将该…

转行学Python开发 怎么快速入门

对于很多转行的新手而言&#xff0c;直接参加培训班是最省时省力的事情&#xff0c;参加培训班既不用担心自己学不会&#xff0c;也不用担心遇到不懂的问题时没有人解答&#xff0c;更重要的是培训班理论实践的教学更贴合实际市场需求。 Python目前是IT行业需求量最大的语言&a…

能够让你装逼的10个Python小技巧

列表推导式 你有一个list&#xff1a; bag [1, 2, 3, 4, 5] 现在你想让所有元素翻倍&#xff0c;让它看起来是这个样子&#xff1a; [2, 4, 6, 8, 10] 大多初学者&#xff0c;根据之前语言的经验会大概这样来做 bag [1, 2, 3, 4, 5] for i in range(len(bag)): bag[i] ba…

GitHub要求所有用户在2023年底前启用双因素身份验证

©网络研究院 GitHub 将要求所有在平台上贡献代码的用户在 2023 年底之前启用双因素身份验证 (2FA) 作为对其帐户的额外保护措施。 双因素身份验证通过在需要输入一次性代码的登录过程中引入额外步骤来提高帐户的安全性。 对于 GitHub 用户来说&#xff0c;账户接管可能…

猿如意中的【Wireshark】网络包分析工具详情介绍

一、工具名称 Wireshark-win64-3.6.5 二、下载安装渠道 Wireshark-win64-3.6.5 通过CSDN官方开发的【猿如意】客户端进行下载安装。 对&#xff0c;你没有看错&#xff0c;就是来自CSDN官方&#xff0c;这次&#xff0c;CSDN果然没有辜负广大技术人的期望&#xff0c;现在…

Redis详解

Redis详解1. 概述1.1 互联网架构的演变历程1.2 Redis入门介绍1.3 Redis/Memcache/MongoDB对比1.3.1 Redis和Memcache1.3.2 Redis和MongoDB1.4 分布式数据库CAP原理1.4.1 CAP简介1.4.2 CAP理论1.4.3 CAP总结2. 下载与安装2.1 下载2.2 安装2.3 安装后的操作2.3.1 后台运行方式2.3…

高级网络应用复习——三层生成树速端口实验(带命令,保姆级)

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.知识点总结 1.生成树STP 2.生成树的算法 3.人为配置的作用…

罗德里格旋转公式 (Rodrigues’ Rotation Formula)

关于三维空间中的旋转&#xff0c;我们以前提到过基于欧拉角的旋转表达矩阵&#xff0c;它们分别描述了围绕 x 轴、y 轴、z 轴旋转后坐标应当如何变化。事实上&#xff0c;我们可以更进一步&#xff0c;推导出一个通用的、围绕过原点的任意轴旋转的公式。 题设 这一节我们来描…

linux-网络-nc命令

目录 概述 nc命令常用参数 nc命令示例 实现TCP/UDP侦听 作为client端发起TCP/UDP连接 服务器之间传输文件 网络测速 概述 在centos中&#xff0c;nc命令是ncat的软链接。 ncat是一个功能丰富的网络实用程序&#xff0c;是为nmap项目编写的&#xff08;Network Mapper&…

12个python超强学习网站!

一、python学习网站 1 CSDN 特点&#xff1a;从免费视频到入门项目&#xff0c;从入门到进阶&#xff0c;学习视频应有尽有&#xff0c;还有Python学习社区&#xff0c;良好的学习和沟通氛围&#xff01; 2 Python123 地址&#xff1a;python123 特点&#xff1a;北京理工…

适合零基础人群学习的Python入门教程,快来学习吧

适合零基础人群学习的Python入门教程学什么&#xff1f;小编为大家准备的Python学习教程&#xff0c;课程主要讲解&#xff1a;Python核心编程、Linux基础、前端开发、Web开发、爬虫开发、人工智能等内容。 对于初学者想更轻松的学好Python开发&#xff0c;爬虫技术&#xff0c…

个人博客 HTML个人介绍网页 学生个人网页设计作品 学生个人网页模板 简单个人主页成品 个人网页制作 HTML学生个人网站作业设计

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

LeetCode刷题日记之栈与队列II

1.有效的括号 题目描述 解题思路 1.定义一个辅助栈stack来存放字符串&#xff0c;再定义一个以符号最为键、值的对象obj 2.循环遍历字符串&#xff0c;判断栈顶元素对应的key在obj中的值是否等于当前遍历值s[i]&#xff0c;如果等于则直接弹栈&#xff0c;不等于则将s[i]值推…

数据结构---判断链表是否有环

判断链表是否有环判断链表是否有环方法1方法2JAVA实现问题扩展1问题扩展2判断链表是否有环 有一个单向链表&#xff0c;链表中有可能出现“环”&#xff0c;就像下图这样。那么&#xff0c;如何用程序来判断该链表是否为有环链表呢&#xff1f; 方法1 创建一个以节点ID为Ke…

大数据Kudu(九):Spark操作Kudu

文章目录 Spark操作Kudu 一、​​​​​​​​​​​​​​添加Maven依赖

【DELM回归预测】基于灰狼算法改进深度学习极限学习机GWO-DELM实现数据回归预测附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

网络请求与数据提取-urllib库

关于网络爬虫&#xff0c;其实就是模拟浏览器向网站服务器发送请求&#xff0c;然后从响应的结果中提取出需要的数据。那么&#xff0c;该如何实现这一流程了&#xff1f;对于初学者来说&#xff0c;可能都不知道该如何入手&#xff0c;学习爬虫时需不需要了解HTTP、TCP、IP 层…

入门:环境安装与部署

容器技术入门 随着时代的发展&#xff0c;Docker也逐渐走上了历史舞台&#xff0c;曾经我们想要安装一套环境&#xff0c;需要花费一下午甚至一整天来配置和安装各个部分&#xff08;比如运行我们自己的SpringBoot应用程序&#xff0c;可能需要安装数据库、安装Redis、安装MQ等…