手撕AVL树

🔥个人主页🔥:鱼骨不是鱼翅-CSDN博客
🌈收录专栏🌈:高阶数据结构_鱼骨不是鱼翅的博客-CSDN博客
🔖 学如逆水行舟,不进则退

目录

一.AVL树的概念

二.平衡因子

三.平衡二叉树怎么调整失衡

1.左旋(RR型):

2.右旋(LL型):

3.左右双旋(LR型):

4.右左双旋(RL型):

5.插入节点有多个祖先失衡

四.平衡二叉树插入操作代码编写(JAVA)

1.创建一个节点类(avlTree类里的成员内部类)

2.创建一个指向根节点的成员变量

3.书写插入方法

3.1实例化节点,判断树是否为空

3.2寻找插入节点的位置

3.3调整平衡

3.4旋转代码

3.4.1左旋代码

3.4.2右旋代码

3.4.3左右双旋代码

3.4.4右左双旋

 五.判断一棵树是否是平衡二叉树

1.中序遍历:

2.判断每个节点左右树高差的绝对值


一.AVL树的概念

AVL树又叫平衡二叉树,他是一种自平衡的二叉搜索树

前提:AVL树得是一颗二叉搜索树

条件:在前提条件下,保证每个节点的左子树和右子树的高度差不超过1

          ((左子树高度-右子树高度)的绝对值<=1)

平衡因子:左子树高度-右子树高的值

AVL树弥补了二叉树搜索树在数据本来就有序时,变成单链表,查询的时间复杂的变为O(n)的缺陷

二.平衡因子

看下面这棵树

上面这棵树它是一棵二叉搜索树,但是不是一棵平衡二叉树

大家可以试着写一下每一个节点的平衡因子:左子树高度-右子树高度

平衡因子:

节点7:2-3=-1;

节点4:1-1=0;

节点1:0-0=0;

节点6:0-0=0;

节点8:0-2=-2;

节点9:0-1=-1;

节点12:0-0=0;

通过观察,我们发现节点8的平衡因子的绝对值为2,大于了1,所以上面这棵树不是一棵平衡二叉树

一棵树要想是平衡二叉树,那么每个节点的平衡因子的取值只能是(-1,0,1)

三.平衡二叉树怎么调整失衡

1.左旋(RR型):

失衡节点的平衡因子为-2,失衡节点右孩子节点的平衡因子为-1

 树2是由数1左旋得到的

当插入节点7时,发现节点3的平衡因子变成了-2,破坏了二叉搜索树的平衡,这时我们通过左旋将其变为树2,维护了二叉搜索树的平衡

左旋:将节点3绕节点4逆时针旋转

下面来看一个复杂一点的左旋

 通过对上面第一个简单左旋,我们发现左旋就是将平衡因子为-2的节点绕其右孩子节点向左旋转或者说是逆时针旋转

然而上面这个左旋,旋转中心是节点8,旋转点是节点6,当旋转点(节点6)绕旋转中心节点(节点8)旋转,节点6旋转作为节点8的左孩子节点时,会和原本节点8的左孩子冲突,这时就需要让节点8的左孩子节点作为节点6的右孩子节点就行

记忆口诀:冲突的左孩作右孩

解释:冲突的左孩是指旋转中心的左孩子,做右孩是让旋转中心的左孩子作为旋转点的右孩子,旋转点作为旋转中心的左孩子

2.右旋(LL型):

失衡节点的平衡因子为2,失衡节点右孩子节点的平衡因子为1

 右旋于左旋类似

上图中的树2是由树1右旋得到的

有了左旋的经验,我们很容易发现

节点12的平衡因子是2,破坏了AVL树的平衡,所以我们需要对这棵树进行调整

旋转中心:节点9

旋转点:节点12

右旋:旋转点绕旋转中心向右旋转(顺时针旋转)

节点12绕节点9顺时针旋转得到树2

下面来看一个复杂点的右旋,原理和左旋复杂一点的类似

 通过对上面第一个简单右旋,我们发现右旋就是将平衡因子为2的节点绕其右孩子节点向右旋转或者说是顺时针旋转

然而上面这个右旋

旋转中心是节点4,旋转点是节点6,当旋转点(节点6)绕旋转中心节点(节点4)旋转,节点6旋转作为节点4的右孩子节点时,会和原本节点4的右孩子冲突,这时就需要让节点4的右孩子节点作为节点6的左孩子节点就行

记忆口诀:冲突的右孩作左孩

解释:冲突的右孩是指旋转中心的右孩子,做左孩是让旋转中心的右孩子作为旋转点的左孩子,旋转点作为旋转中心的右孩子

3.左右双旋(LR):

失衡节点的平衡因子为2,失衡节点右孩子节点的平衡因子为-1

左右双旋需要做两步:

第一步:对失衡节点的左孩子节点进行左旋

第二步:对失衡节点进行右旋

第一步解析:

对失衡节点的左孩子节点进行左旋,找到失衡节点6,他的左孩子节点为节点3,对其进行左旋

旋转点:节点3

旋转中心:节点5

旋转:节点3绕节点5逆时针旋转,节点3作为节点5的左孩子节点与节点4冲突,节点4作为旋转点节点3的右孩子(冲突的左孩作右孩)

第二步解析:

对失衡节点进行右旋

旋转点:节点6

旋转中心:节点5

旋转:节点6绕节点5顺时针旋转

4.右左双旋(RL):

失衡节点的平衡因子为-2,失衡节点右孩子节点的平衡因子为1

右左双旋同样需要两步完成

第一步:对失衡节点的有孩子节点进行右旋

第二步:对失衡节点进行左旋

第一步解析:

对失衡节点的右孩子节点进行右旋,找到失衡节点4,他的右孩子节点为节点8,对其进行右旋

旋转点:节点8

旋转中心:节点5

旋转:节点8绕节点5顺时针旋转,节点8作为节点5的右孩子节点与节点7冲突,节点7作为旋转点节点8的左孩子(冲突的右孩作左孩)

第二步解析:

对失衡节点进行左旋

旋转点:节点4

旋转中心:节点5

旋转:节点4绕节点5逆时针旋转

5.插入节点有多个祖先失衡

当多个祖先同时失衡时,只需要调整最近失衡的节点就可使平衡二叉树保持平衡

其他失衡节点会自动变平衡

四.平衡二叉树插入操作代码编写(JAVA)

1.创建一个节点类(avlTree类里的成员内部类)

创建一个节点类,用来存储一个应该具有的信息

    static class treeNode{int val;//节点存储的值treeNode left;//左孩子节点treeNode right;//右孩子节点treeNode parent;//父亲节点int bf;//平衡因子//构造方法public treeNode(int val) {this.val = val;}}

一个节点类里面应该具有

1.该节点所存储的值

2.左右孩子节点

3.父亲节点

4.平衡因子

5.构造方法

2.创建一个指向根节点的成员变量

  public treeNode root;

3.书写插入方法

3.1实例化节点,判断树是否为空

        treeNode node=new treeNode(val);//调用构造方法,实例化节点if(root==null){root=node;}//如果树为空,就将新创建的节点作为根节点

3.2寻找插入节点的位置

        treeNode parent=null;//parent作为插入节点的父亲节点treeNode cur=root;//cur用作寻找插入节点的位置while(cur!=null){//当cur为空时,cur所指位置就是插入节点位置if(cur.val<val){//如果cur所指节点的值小于插入节点的值,就向cur所指节点的右树寻找parent=cur;//先让parent指向curcur=cur.right;//cur再指向右孩子节点}else if(cur.val==val){//如果cur所指节点的值和插入节点值相等,直接返回falsereturn false;//这是因为平衡二叉树也是二叉搜索树,在二叉搜索树里,不能存在两个值相同的节点}else {//如果cur所指节点的值大于插入节点的值,就向cur的左树去寻找parent=cur;cur=cur.left;}}
//当找到插入位置时,即cur==null,需要判断一下插入节点的值和parent值的大小关系,大于parent,插入parent的右孩子节点,反之插入左孩子节点if(parent.val>val){parent.left=node;node.parent=parent;}else {parent.right=node;node.parent=parent;}cur=node;//最后记得将cur指向插入节点

特别注意:最后一定要将cur指向插入节点,为接下来调整平衡做准备

3.3调整平衡

        while(parent!=null){if(cur==parent.left){parent.bf++;}else {parent.bf--;}if(parent.bf==0){return true;}else if(parent.bf==-1||parent.bf==1){cur=parent;parent=parent.parent;}else {if(parent.bf==2){if(cur.bf==1){//右旋
revolveLL(parent);}else {//左右双旋
revolveLR(parent);}}else {if(cur.bf==1){//右左双旋
revolveRL(parent);}else {//左旋
revolveRR(parent);}}break;}}
return true;

 1.向上调整平衡因子,如果cur是parent的左孩子节点,那么parent的平衡因子++,如果是右孩子节点,parent的平衡因子--

2.每调整一个父亲节点的平衡因子,就判断一下,如果父亲节点的平衡因子为0,说明此时以父亲节点为根节点的左右子树高度相等,处于平衡状态

如果父亲节点的平衡因子的绝对值为1,就需要继续往上进行平衡因子的调节

如果父亲节点的平衡因子的绝对值是2,那么说明以父亲节点为根节点的树失衡,就需要根据父亲节点的平衡因子和cur的平衡因子,对树进行旋转操作

3.当执行完旋转操作调节平衡后,树就平衡了,直接跳出循环,返回true

当父亲节点的平衡因子为2时:

说明左树高,这时cur一定是左孩子节点,在通过cur的平衡因子判断失衡模型

如果cur的平衡因子是1,那么就是LL型,需要进行右旋

如果cur的平衡因子是-1,那么就是LR型,需要进行左右双旋


当父亲节点的平衡因子为-2时:

说明右树高,这时cur一定是右孩子节点,在通过cur的平衡因子判断失衡模型

如果cur的平衡因子是1,那么就是RL型,需要进行右左双旋

如果cur的平衡因子是-1,那么就是RR型,需要进行左旋

3.4旋转代码

3.4.1左旋代码
    private void revolveRR(treeNode parent){
treeNode subR=parent.right;//旋转中心
treeNode subRL=subR.left;//冲突的左孩//冲突的左孩是否为空if(subRL!=null){subRL.parent=parent;}parent.right=subRL;//父亲节点是否为根节点if(parent==root){subR.parent=null;root=subR;}else {treeNode pParent=parent.parent;if(parent==pParent.left){pParent.left=subR;}else {pParent.right=subR;}subR.parent=pParent;}//处理旋转点和旋转中心的关系parent.parent=subR;subR.left=parent;//修改平衡因子parent.bf=0;subR.bf=0;}

 左旋,无论是简单的还是复杂的,都只有旋转点和旋转中心的平衡因子被修改

3.4.2右旋代码
    private void revolveLL(treeNode parent){
treeNode subL=parent.left;//旋转中心
treeNode subLR=subL.right;//冲突的右孩//处理右孩为空的情况
if(subLR!=null){subLR.parent=parent;
}
parent.left=subLR;//右孩和旋转点建立联系
//处理旋转点是否是根节点的情况if(parent==root){root=subL;subL.right=parent;}else{treeNode pParent=parent.parent;if(parent==pParent.left){pParent.left=subL;}else {pParent.right=subL;}subL.parent=pParent;}//旋转中心和旋转点建立联系parent.parent=subL;subL.right=parent;//修改平衡因子
subL.bf=parent.bf=0;}

对于右旋操作来说,也只有旋转中心和旋转点的平衡因子需要修改 

3.4.3左右双旋代码
    private void revolveLR(treeNode parent){treeNode subL=parent.left;treeNode subLR=subL.right;int bf=subLR.bf;
revolveRR(parent.left);//先左旋父亲节点的左孩子节点
revolveLL(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;}}

左右双旋时,需要根据subLR的平衡因子来确定最后怎么修改平衡因子 

 对于这种subLR的平衡因子为0的,在右旋和左旋过程中,旋转点和旋转中心的节点,已经被修改为0了,就无需再对其进行修改了

3.4.4右左双旋
    private void revolveRL(treeNode parent){
treeNode subR=parent.right;
treeNode subRL=subR.left;
int bf=subRL.bf;
revolveLL(parent.right);
revolveRR(parent);
if(bf==1){parent.bf=0;subRL.bf=0;subR.bf=-1;
}else if(bf==-1){parent.bf=1;subRL.bf=0;subR.bf=0;
}}

右左双旋时,同样也需要根据subRL的平衡因子对调整后的平衡因子做对应的修改

对于这种subRL的平衡因子为0的,在右旋和左旋过程中,旋转点和旋转中心的节点,已经被修改为0了,就无需再对其进行修改了

 五.判断一棵树是否是平衡二叉树

平衡二插树首先是一棵二叉搜索树,所以他的中序遍历是有序的,在其次就是他的每个节点的左右树高的差的绝对值不能大于1,通过这两个特性,我们就能判定一棵树是否是平衡二叉树

1.中序遍历:

    public void inOrder(treeNode root){if(root==null){return;}inOrder(root.left);System.out.print(root.val+" ");inOrder(root.right);}

2.判断每个节点左右树高差的绝对值

    public boolean isBalanced(treeNode root){if(root==null){return true;}int leftH=height(root.left);int rightH=height(root.right);//判断左右树高差的绝对值是否小于等于1,并且递归判断每一个节点是否满足要求return Math.abs(leftH-rightH)<=1&&isBalanced(root.left)&&isBalanced(root.right);}//获取左右树高private int height(treeNode node){if(node==null){return 0;}int leftH=height(node.left);int rightH=height(node.right);return Math.max(leftH,rightH)+1;}

六.AVL树性能分析

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

                                                                                                                                   努力吧!少年 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自动驾驶系列—从IMU到惯性定位算法:自动驾驶精准定位的幕后科技

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

哪些因素会影响移动电源自动测试的结果?-纳米软件

一、USB 连接线对移动电源测试结果的影响 在移动电源输出电压的测试中&#xff0c;辅助用的 USB 连接线对测试的影响常常被测试工程师所忽视。不同的 USB 线缆连接可能会导致移动电源输出电压的测试结果不同。这是因为不同的 USB 线缆在材质、长度、线径等方面存在差异&#xf…

C语言 | Leetcode C语言题解之第454题四数相加II

题目&#xff1a; 题解&#xff1a; struct hashTable {int key;int val;UT_hash_handle hh; };int fourSumCount(int* A, int ASize, int* B, int BSize, int* C, int CSize, int* D, int DSize) {struct hashTable* hashtable NULL;for (int i 0; i < ASize; i) {for (…

分辨率提高4到8倍!AI高清修复工具-upscayl使用方法!

你还在为手中的模糊照片苦恼吗&#xff1f; 是不是想把老照片或低分辨率的图片用于大尺寸印刷&#xff0c;却因为画质糟糕而无从下手&#xff1f; 现在你不再需要高深的Photoshop技能&#xff0c;也不用花费巨资找人修图。借助AI高清修复工具Upscayl&#xff0c;只需几秒钟&am…

C语言预处理详解(上)(30)

文章目录 前言一、预定义符号二、#define定义标识符三、#define定义宏四、#define的替换规则五、带有副作用的宏六、宏和函数的对比七、#undef的作用八、# 和#的作用##的作用 总结 前言 C语言的入门学习差不多要到尾声了&#xff0c;感觉如何呢~   前文说编译的第一步就是预编…

计算机网络:计算机网络概述 —— 描述计算机网络的参数

文章目录 数据量性能指标速率带宽数据传输速率 吞吐量时延分析时延问题 时延带宽积往返时间利用率丢包率丢包的情况 抖动可用性可靠性安全性 计算机网络是现代信息社会的基础设施&#xff0c;其性能和可靠性对各类应用至关重要。为了理解和优化计算机网络&#xff0c;我们需要深…

python习题2

1、输出一个年份&#xff0c;判断其是不是闰年 #输入一个年份&#xff0c;判断其是否是闰年 y eval(input()) if y%4 0 and y%100 ! 0:print("是") elif y%4000:print("是") else:print("不是") 2、模拟智能客服&#xff1a; 按1查询账户余额…

【linux】麒麟v10安装prometheus监控(ARM架构)

Prometheus介绍 Prometheus 是一个开源的系统监控和警报工具包&#xff0c;最初由 SoundCloud 开发&#xff0c;现在是一个独立的开源项目&#xff0c;并且是云原生计算基金会&#xff08;CNCF&#xff09;的一部分。Prometheus 以其强大的数据模型和灵活的查询语言&#xff0…

Jedis多线程环境报错:redis Could not get a resource from the pool 的主要原因及解决办法。

本篇文章主要讲解&#xff0c;Jedis多线程环境报错&#xff1a;redis Could not get a resource from the pool 的主要原因及解决办法。 作者&#xff1a;任聪聪 日期&#xff1a;2024年10月6日01:29:21 报错信息&#xff1a; 报文&#xff1a; redis Could not get a resou…

[SQL] 安装

一 Windows 1.1 下载 进入Mysql的官方网站,点击下载->找到社区版本 选择对应操作系统进行下载。 点击下载 选择直接下载即可 1.2 安装 选择Full安装&#xff1a; MySQL服务器、客户端程序和其他附加工具如果只需要服务端那就选择Server only即可 点击执行,等待组件下载完…

PostgreSQL 任意命令执行漏洞(CVE-2019-9193)

记一次授权攻击通过PostgreSql弱口令拿到服务器权限的事件。 使用靶机复现攻击过程。 过程 在信息收集过程中&#xff0c;获取到在公网服务器上开启了5432端口&#xff0c;尝试进行暴破&#xff0c;获取到数据库名为默认postgres&#xff0c;密码为1 随后连接进PostgreSql …

IDEA里面的长截图插件

1.我的悲惨经历 兄弟们啊&#xff0c;我太惨了&#xff0c;我刚刚在准备这个继承和多态的学习&#xff0c;写博客的时候想要截图代码&#xff0c;因为这个代码比较大&#xff0c;一张图截取不下来&#xff0c;所以需要长截图&#xff0c;之前使用的qq截图突然间拉胯&#xff0…

【RK3588】rknpu驱动流程

画图工具 &#xff1a; https://pixso.cn/

第十四章 Redis之全局唯一ID(分布式集群)

目录 一、概念 ‌二、全局唯一ID的生成方法‌ 三、Redis生成全局ID 3.1. 生成策略 3.2. 代码 一、概念 全局唯一ID是指在分布式系统中&#xff0c;每个实体都有一个唯一的标识符&#xff0c;确保在不同的节点或服务之间能够唯一标识一个实体。这种唯一性对于数据的一致性…

OpenCV库模块解析

1.OpenCV库每个模块解析 2.OpenCV的常用函数 它为计算机视觉应用程序提供了一个通用的基础设施&#xff0c;并加速了在商业产品中使用机器感知。作为BSD许可的产品&#xff0c;OpenCV使企业可以很容易地利用和修改代码。该库拥有超过2500个优化算法&#xff0c;其中包括经典和最…

Android -- [SelfView] 自定义多色渐变背景板

Android – 自定义多色渐变背景板 前言&#xff1a; Android 自带的 xml 文件内 gradient 设置渐变最多只有三种颜色&#xff0c;使用方便但范围受限&#xff0c;不能很好满足各种需求&#xff1b; 本款多色渐变背景板应运而生&#xff1a;* 1. 支持圆角模式&#xff0c;矩形模…

Windows环境下CTRL+C信号处理函数的执行线程

1. 捕获CTRLC 有时候我们希望自己的程序被CTRLC以后&#xff0c;可以先执行一些收尾的工作才结束&#xff0c;比如释放动态内存&#xff0c;关闭网络端口、保存一些状态日志等等&#xff0c;可以用到C的signal的机制。 例程如下&#xff1a; #include <iostream> #inc…

【工具】前端js数字金额转中文大写金额

【工具】前端js数字金额转中文大写金额 代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>金额转…

WIFI网速不够是不是光猫的“路由模式”和“桥接模式”配置错了?

光猫&#xff08;光纤调制解调器&#xff09;是一种用于将光纤信号转换为数字信号的设备&#xff0c;通常用于家庭或企业网络中。光猫可以在不同的工作模式下运行&#xff0c;其中最常见的两种模式是“路由模式”和“桥接模式”。以下是这两种模式的详细解释及其优缺点。 一、路…

『网络游戏』服务器向客户端分发消息【20】

对服务器添加System引用 修改脚本&#xff1a;LoginSys.cs 修改脚本&#xff1a;NetSvc.cs 修改脚本&#xff1a;ServerSession.cs 修改脚本&#xff1a;GameMsg.cs 修改脚本&#xff1a;MsgPack.cs 修改脚本&#xff1a;LoginSys.cs 修改脚本&#xff1a;ServerRoot.cs 修改脚…