当前位置: 首页 > news >正文

数据结构奇妙旅程之二叉平衡树进阶---AVL树

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱
ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客
本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶
个人主页:xiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客
系列专栏:xiaoxie的JAVA系列专栏——CSDN博客●'ᴗ'σσணღ*
我的目标:"团团等我💪( ◡̀_◡́ ҂)" 

( ⸝⸝⸝›ᴥ‹⸝⸝⸝ )欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​+关注(互三必回)!

 一.AVL树的概念

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

1.它的左右子树都是AVL树

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

这里我们规定节点的左孩子的平衡因子为--,右孩子为++;

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

二.AVL树节点的定义

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

当前节点的平衡因子=右子树高度-左子树的高度。但是,不是每棵树,都必须有平衡因子,这只是其中的一种实现方式,并且这只是一种表示方式。

三.AVL树节点的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可 以分为两步:

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

 TreeNode node = new TreeNode(val);if(root == null) {root = node;root.parent = null;}TreeNode parent = null;TreeNode cur = root;while (cur != null) {if(node.val < cur.val) {parent = cur;cur = cur.left;} else if (node.val > cur.val) {parent = cur;cur = cur.right;}else {System.out.println("节点值 " + val + " 已经存在于树中,不进行插入操作");return false;}}//cur == null//找到node是parent的左孩子还是右孩子if(node.val < parent.val) {parent.left = node;}else {parent.right = node;}node.parent = parent;cur = node;

 2.调整节点的平衡因子

新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树 的平衡性.

此时cur节点插入,parent节点的平衡因子就需要调整, 有以下两种情况

1.Cur插入到Parent的左侧,只需给Parent的平衡因子-1即可

2. 如果Cur插入到Parent的右侧,只需给Parent的平衡因子+1即可

此时:Parent的平衡因子可能有三种情况:0,正负1, 正负2

1.如果parent的平衡因子为0,那么就说明在插入前parent的平衡因子为正负1,插入后被调整成0,此时满足 AVL树的性质,插入成功

2.如果Parent的平衡因子为正负1,说明插入前Parent的平衡因子一定为0,插入后被更新成正负1,此 时以Parent为根的子树的高度增加,需要继续向上更新

3.如果Parent的平衡因子为正负2,则Parent的平衡因子违反平衡树的性质,需要对其进行旋转处理

  while (parent != null) {//计算节点的平衡因子//如果是左孩子就--//右孩子就++;if(cur == parent.left) {parent.bf--;}else {parent.bf++;}//根据不同的平衡因子有不同的情况//1.如果平衡因子为0if(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 {rotateRL(parent);//cur.bf = -1}}else {//parent.bf == -2//就代表左树高,需要调整左树的高度,就是左单旋,或者左右双旋if(cur.bf == 1) {rotateLR(parent);}else {//cur.bf == -1rotateRight(parent);}}}

 四.AVL树的旋转(重点)

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据 节点插入位置的不同,AVL树的旋转分为四种:

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

上图在插入前,AVL树是平衡的,新节点插入到20的左子树(注意:此处不是左孩子)中,20左子树增加 了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层, 即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值 一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下 几种情况需要考虑: 1. 30节点的右孩子可能存在,也可能不存在 2. 60可能是根节点,也可能是子树 如果是根节点,旋转完成后,要更新根节点 如果是子树,可能是某个节点的左子树,也可能是右子树 

 /*** 右单旋左树高的话需要调整左树的高度* @author xiaoxie* @date 2024/3/6 15:01* @param parent*/private void rotateRight(TreeNode parent) {TreeNode subL = parent.left;TreeNode subLR = subL.right;subL.right = parent;parent.left = subLR;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;}parent.bf = 0;subL.bf = 0;}

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

 

上图在插入前,AVL树是平衡的,新的节点80,插入到70的右子树,70右子树增加 了一层,导致以30为根的二叉树不平衡,要让30平衡,只能将30右子树的高度减少一层,左子树增加一层, 即将右子树往上提,这样30转下来,因为30比60小,只能将其放在60的左子树,而如果60有左子树,左子树根的值 一定大于30,小于60,只能将其放在30的右子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下 几种情况需要考虑: 1. 60节点的右孩子可能存在,也可能不存在 2. 30可能是根节点,也可能是子树 如果是根节点,旋转完成后,要更新根节点 如果是子树,可能是某个节点的左子树,也可能是右子树 

/** 左单旋* 左旋,右树高的情况,需要调整右树的高度* @author xiaoxie* @date 2024/3/6 14:14* @param parent*/private void rotateLeft(TreeNode parent) {TreeNode subR = parent.right;TreeNode subRL = subR.left;subR.left = parent;parent.right = subRL;if(subRL != null) {subRL.parent = parent;}//先记录parent节点的父亲节点TreeNode Pparent = parent.parent;parent.parent = subR;if(parent == root) {root = subR;subR.parent = null;}else {if(Pparent.left == parent) {Pparent.left = subR;}else {Pparent.right = subR;}subR.parent = Pparent;}parent.bf = 0;subR.bf = 0;}

3.新节点插入较高左子树的右侧:先左单旋再右单旋【左右双旋】

当一个节点的左子树比右子树高度高2个以上时,且这个节点的左子节点的右子树高于左子树时,需要进行左右双旋

1.情况一 subLR的平衡因子为1

 

在上图插入前,AVL树是平衡的,但在插入节点50后,插入到40的右子树上时,40的左子树的高度就增加一层了,以60为根的子树就不平衡了,这个时候我们就需要增加60的右子树的高度,降低左子树的高度,我们就需要用到右单旋,但60节点的左子节点的右子树高于左子树时,我们仅仅通过简单的右单旋并不能使这棵树变成高度平衡的二叉搜索树,我们就需要先进行左单旋,使这个节点的左节点的左子树和右子树的平衡后再进行右单旋,可以从上图看出,在进行左单旋后这颗树的情况和情况1一样,只是在最后需要我们调整 subL,parent,subLR的平衡因子即可

2.情况二subLR的平衡因子为-1

 

情况二和情况一的旋转过程大致一样,只不对 subL,parent,subLR的平衡因子的调整不同

/*** 左右双旋* @author xiaoxie* @date 2024/3/6 16:04* @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) {subLR.bf = 0;subL.bf = -1;parent.bf = 0;}else if(bf == -1) {subLR.bf = 0;subL.bf = 0;parent.bf = 1;}}

4.新节点插入较高右子树的左侧---右左:先右单旋再左单旋【右左双旋】

当一个节点的右子树比左子树高度高2个以上时,且这个节点的右子节点的左子树高于右子树时,需要进行右左双旋

1.情况一 subRL的平衡因子为1

 

在上图插入前,AVL树是平衡的,但在插入节点75后,插入到70的右子树上时,70的右子树的高度就增加一层了,以60为根的子树就不平衡了,这个时候我们就需要增加60的左子树的高度,降低右子树的高度,我们就需要用到左单旋,但60节点的右子节点的左子树高于右子树时,我们仅仅通过简单的左单旋并不能使这棵树变成高度平衡的二叉搜索树,我们就需要先进行右单旋,使这个节点的右节点的右子树和左子树的平衡后再进行左旋,可以从上图看出,在进行右单旋后这颗树的情况和情况2一样,只是在最后需要我们调整 subR,parent,subRL的平衡因子即可 

2.情况二subRL的平衡因子为-1

 

况二和情况一的旋转过程大致一样,只不对 subR,parent,subRL的平衡因子的调整不同

/*** 右左双旋* @author xiaoxie* @date 2024/3/6 16:19* @param parent*/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 = 0;subR.bf = 1;subRL.bf = 0;}else if(bf == 1) {parent.bf = -1;subRL.bf = 0;subR.bf = 0;}}

 5.旋转的总结

新节点插入后,假设以Parent为根的子树不平衡,即Parent的平衡因子为2或者-2,分以下情况考虑

1. Parent的平衡因子为2,说明Parent的右子树高,设Parent的右子树的根为SubR 当SubR的平衡因子为1时,执行左单旋 当SubR的平衡因子为-1时,执行右左双旋

2. Parent的平衡因子为-2,说明Parent的左子树高,设Parent的左子树的根为SubL 当SubL的平衡因子为-1是,执行右单旋 当SubL的平衡因子为1时,执行左右双旋

即:Parent与其较高子树节点的平衡因子时同号时单旋转,异号时双旋转。

旋转完成后,原Parent为根的子树个高度降低,已经平衡,不需要再向上更新

五.总的代码实现及总结

1.Java代码实现

public class AVLTree {public static class TreeNode {public int val;public TreeNode left;public TreeNode right;public int bf;public TreeNode parent;public TreeNode(int val) {this.val = val;}}public TreeNode root;/*** 插入节点,并调整平衡因子* 使用旋转的方法调整平衡因子* @author xiaoxie* @date 2024/3/6 13:13* @param val* @return boolean*/public boolean insertNode(int val) {TreeNode node = new TreeNode(val);if(root == null) {root = node;root.parent = null;}TreeNode parent = null;TreeNode cur = root;while (cur != null) {if(node.val < cur.val) {parent = cur;cur = cur.left;} else if (node.val > cur.val) {parent = cur;cur = cur.right;}else {System.out.println("节点值 " + val + " 已经存在于树中,不进行插入操作");return false;}}//cur == null//找到node是parent的左孩子还是右孩子if(node.val < parent.val) {parent.left = node;}else {parent.right = node;}node.parent = parent;cur = node;while (parent != null) {//计算节点的平衡因子//如果是左孩子就--//右孩子就++;if(cur == parent.left) {parent.bf--;}else {parent.bf++;}//根据不同的平衡因子有不同的情况//1.如果平衡因子为0if(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 {rotateRL(parent);//cur.bf = -1}}else {//parent.bf == -2if(cur.bf == 1) {rotateLR(parent);}else {//cur.bf == -1rotateRight(parent);}}}}return true;}/** 左单旋* 左旋,右树高的情况,需要调整右树的高度* @author xiaoxie* @date 2024/3/6 14:14* @param parent*/private void rotateLeft(TreeNode parent) {TreeNode subR = parent.right;TreeNode subRL = subR.left;subR.left = parent;parent.right = subRL;if(subRL != null) {subRL.parent = parent;}//先记录parent节点的父亲节点TreeNode Pparent = parent.parent;parent.parent = subR;if(parent == root) {root = subR;subR.parent = null;}else {if(Pparent.left == parent) {Pparent.left = subR;}else {Pparent.right = subR;}subR.parent = Pparent;}parent.bf = 0;subR.bf = 0;}/*** 右单旋左树高的话需要调整左树的高度* @author xiaoxie* @date 2024/3/6 15:01* @param parent*/private void rotateRight(TreeNode parent) {TreeNode subL = parent.left;TreeNode subLR = subL.right;subL.right = parent;parent.left = subLR;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;}parent.bf = 0;subL.bf = 0;}/*** 左右双旋* @author xiaoxie* @date 2024/3/6 16:04* @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) {subLR.bf = 0;subL.bf = -1;parent.bf = 0;}else if(bf == -1) {subL.bf = 0;parent.bf = 1;subLR.bf = 0;}}/*** 右左双旋* @author xiaoxie* @date 2024/3/6 16:19* @param parent*/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 = 0;subR.bf = 1;subRL.bf = 0;}else if(bf == 1) {parent.bf = -1;subRL.bf = 0;subR.bf = 0;}}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;}/*** 验证是否正确AVLTree* @author xiaoxie* @date 2024/3/6 16:49* @param root* @return boolean*/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);}
}

2.AVL树总结

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

以上就是博主关于AVL树的全部总结了,希望能够对你有所帮助!

 

 

 

相关文章:

  • scrapy的基本使用介绍
  • CUDA入门之统一内存
  • 学习大数据,所需要Java基础(9)
  • taosdb快速入门
  • Docker的基本概念和优势
  • 【鸿蒙 HarmonyOS 4.0】常用组件:List/Grid/Tabs
  • 常见doc命令使用
  • 2024蓝桥杯每日一题(二分)
  • torchrun常见参数
  • 【论文阅读】ACM MM 2023 PatchBackdoor:不修改模型的深度神经网络后门攻击
  • 颜色检测python项目
  • xlsx.js读取本地文件,按行转成数组数据
  • 手机App防沉迷系统C卷(JavaPythonC++Node.jsC语言)
  • UE5.1_TimeLine
  • yudao-cloud 学习笔记
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • iOS 系统授权开发
  • Python十分钟制作属于你自己的个性logo
  • underscore源码剖析之整体架构
  • Vue官网教程学习过程中值得记录的一些事情
  • Vue实战(四)登录/注册页的实现
  • 聊聊hikari连接池的leakDetectionThreshold
  • 前嗅ForeSpider采集配置界面介绍
  • 推荐一个React的管理后台框架
  • 赢得Docker挑战最佳实践
  • 用Canvas画一棵二叉树
  • 原生 js 实现移动端 Touch 滑动反弹
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • #HarmonyOS:软件安装window和mac预览Hello World
  • (06)金属布线——为半导体注入生命的连接
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (Python) SOAP Web Service (HTTP POST)
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (理论篇)httpmoudle和httphandler一览
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (四)图像的%2线性拉伸
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (一)Neo4j下载安装以及初次使用
  • (原創) 如何將struct塞進vector? (C/C++) (STL)
  • (转)使用VMware vSphere标准交换机设置网络连接
  • (转载)hibernate缓存
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .dwp和.webpart的区别
  • .Net Core和.Net Standard直观理解
  • .net framework 4.0中如何 输出 form 的name属性。
  • .pyc文件还原.py文件_Python什么情况下会生成pyc文件?
  • /var/spool/postfix/maildrop 下有大量文件
  • ??javascript里的变量问题
  • @modelattribute注解用postman测试怎么传参_接口测试之问题挖掘