20分钟搞定平衡二叉树(AVL树)【超详细】

本文介绍了树结构的基础概念,包括节点、边、路径等,并深入探讨了二叉搜索树的特性,如查找、插入和删除操作。接着,文章阐述了AVL树作为平衡二叉树的重要性,分析了失衡情况及其调整策略。AVL树通过限制节点高度差来确保高效的查找性能,但在频繁插入和删除时可能需要频繁调整。因此,引入了红黑树作为更灵活的选择。最后,讨论了平衡树与红黑树在实际应用中的优劣。
摘要由CSDN通过智能技术生成


一、树结构入门


1.什么是树?

树(tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点通过连接它们的边组成一个具有层次关系的集合。
把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

树有很多种,向上面的一个节点有多余两个的子节点的树,称为多路树,而每个节点最多只能有两个子节点的一种形式称为二叉树。
在这里插入图片描述①、节点:上图的圆圈,比如A,B,C等都是表示节点。节点一般代表一些实体,在java面向对象编程中,节点一般代表对象。
②、边:连接节点的线称为边,边表示节点的关联关系。一般从一个节点到另一个节点的唯一方法就是沿着一条顺着有边的道路前进。在Java当中通常表示引用。

2.树结构常用术语

在这里插入图片描述
① 路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。
  ②根:树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。
  ③父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点
  ④子节点:一个节点含有的子树的节点称为该节点的子节点;F、G是C节点的子节点。
  ⑤兄弟节点:具有相同父节点的节点互称为兄弟节点;F、G节点互为兄弟节点。
  ⑥叶节点:没有子节点的节点称为叶节点,也叫叶子节点,比如上图的H、E、F、G都是叶子节点。
  ⑦子树:每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中。
  ⑧节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。
  ⑨深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;(从上往下看)
  ⑩高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;(从下往上看)


3.二叉搜索树


二叉树:树的每个节点最多只能有两个子节点。
二叉搜索树: 如果我们给二叉树加一个额外的条件,就可以得到一种被称作二叉搜索树(binary search tree)的特殊二叉树。
二叉搜索树要求:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树。
在这里插入图片描述
二叉搜索树-查找节点:

查找某个节点,我们必须从根节点开始查找:
  ①查找值比当前节点值大,则搜索右子树;
  ②查找值等于当前节点值,停止搜索(终止条件);
  ③查找值小于当前节点值,则搜索左子树;

二叉搜索树-插入节点:

要插入节点,必须先找到插入的位置。与查找操作相似,由于二叉搜索树的特殊性,待插入的节点也需要从根节点开始进行比较,小于根节点则与根节点左子树比较,反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置。

二叉搜索树-遍历节点:
遍历树是根据一种特定的顺序访问树的每一个节点。比较常用的有前序遍历,中序遍历和后序遍历。而二叉搜索树最常用的是中序遍历。
  ①中序遍历:左子树——》根节点——》右子树
  ②前序遍历:根节点——》左子树——》右子树
  ③后序遍历:左子树——》右子树——》根节点
在这里插入图片描述
二叉搜索树-查找最大值和最小值
要找最小值,先找根的左节点,然后一直找这个左节点的左节点,直到找到没有左节点的节点,那么这个节点就是最小值。
同理要找最大值,一直找根节点的右节点,直到没有右节点,则就是最大值。
在这里插入图片描述

二叉搜索树-删除节点:

删除节点是二叉搜索树中最复杂的操作,删除的节点有三种情况,前两种比较简单,但是第三种却很复杂。
(1)该节点是叶节点(没有子节点)
(2)该节点有一个子节点
(3)该节点有两个子节点
  ①、删除没有子节点的节点
要删除叶节点,只需要改变该节点的父节点引用该节点的值,即将其引用改为 null 即可。
在这里插入图片描述
  ②、删除有一个子节点的节点
删除有一个子节点的节点,我们只需要将其父节点原本指向该节点的引用,改为指向该节点的子节点即可。
在这里插入图片描述
  ③、删除有两个子节点的节点
在这里插入图片描述
当删除的节点存在两个子节点,那么删除之后,两个子节点的位置我们就没办法处理了。

既然处理不了,我们就想到一种办法,用另一个节点来代替被删除的节点,那么用哪一个节点来代替呢?

我们知道二叉搜索树中的节点是按照关键字来进行排列的,某个节点的关键字次高节点是它的中序遍历后继节点。

用后继节点来代替删除的节点,显然该二叉搜索树还是有序的。

在这里插入图片描述
那么如何找到删除节点的中序后继节点呢?

其实我们稍微分析,这实际上就是要找比删除节点关键值大的节点集合中最小的一个节点,只有这样代替删除节点后才能满足二叉搜索树的特性。
后继节点也就是:比删除节点大的最小节点。

④、删除有必要吗?
通过上面的删除分类讨论,我们发现删除其实是挺复杂的,那么其实我们可以不用真正的删除该节点,只需要在Node类中增加一个标识字段isDelete,当该字段为true时,表示该节点已经删除,反之则没有删除。这样删除节点就不会改变树的结构了。影响就是查询时需要判断一下节点是否已被删除。

二叉搜索树-时间复杂度分析:

1.回顾经典-二分查找算法
[1,2,3,4,5,6,7,8,9。。。。。。。100]
暴力算法:数组有序时性能不错,数组乱序时性能爆炸…
二分查找算法:数据源必须是有序数组,性能非常不错,每次迭代查询可以排除掉一半的结果。

2.二分查找算法最大的缺陷是什么?
强制依赖 有序数组,性能才能不错。

3.数组有什么缺陷?
没有办法快速插入,也没有办法扩容

4.那怎么才能拥有二分查找的高性能又能拥有链表一样的灵活性?
二叉搜索树!!

5.二分查找算法时间复杂度推算过程:

第几次查询剩余待查询元素数量
1N/2
2N/(2^2)
3N/(2^3)
KN/(2^K)

从上表可以看出N/(2^K) 肯定是大于等于1,也就是N/(2^K)>=1,我们计算时间复杂度是按照最坏的情况进行计算,也就是是查到剩余最后一个数才查到我们想要的数据,也就是
N/(2^K)=1 => 2^K = N => K = log2 (N) => 二分查找算法时间复杂度:O(log2(N)) => O(logN)

普通二叉搜索树致命缺陷:
在这里插入图片描述
Q1: 这颗二叉树查询效率咋样呢?
A1: O(N)
Q2: 怎么解决 二叉搜索树 退化成线性链表的问题?
A2: 如果插入元素时,树可以自动调整两边平衡,会保持不错的查找性能。


二、平衡二叉树(AVL树)


1. AVL树简介

AVL树(平衡二叉树)有什么特点?

1、具有二叉查找树的全部特性
2、每个节点的左子树和右子树的高度差至多等于1,即每个节点的左子树的高度减去右子树的高度的差只能是-1,0,1(此高度差又称平衡因子,1代表左子树的高度比右子树高1),且左子树和右子树也是平衡二叉树。
3.对于一棵有N个节点的AVL树,其高度保持在O(log2N)

正确示例:
在这里插入图片描述
错误示例:

数值为8的节点左子树高度为2,右子树高度为0,2-0=2>1,不满足平衡二叉树的定义,该树不是AVL树。
在这里插入图片描述
平衡树基于这种特点就可以保证不会出现大量节点偏向于一边的情况了!

当在平衡二叉树上插入或删除某一节点时,可能会导致失衡,即平衡因子的绝对值大于1。此时必须同通过左旋或右旋,使这棵树再次左右保持一定的平衡。


2. 失衡二叉排序树的分析与调整


由于插入新节点C导致A节点失衡,此时需要重新平衡,可将所有可能归类为4种情况:
①对A的左儿子的左子树进行一次插入C(LL)
②对A的左儿子的右子树进行一次插入C(LR)
③对A的右儿子的左子树进行一次插入C(RL)
④对A的右儿子的右子树进行一次插入C(RR)
在这里插入图片描述

(图:https://www.bilibili.com/video/BV1Zt411q7LU?spm_id_from=333.788.b_636f6d6d656e74.4)

其中:
A:失衡节点(不止一个失衡节点时,最小失衡子树的根节点为失衡节点)
B:A节点的孩子,C节点的父亲
C:插入的新节点

调整的原则是: 降低树的高度;保持二叉排序树的性质

以LL型为例,失衡时树的层次是3层,节点数值的大小C<B<A。
根据调整的原则,树的高度从3降为2, 同时要保证节点数值的大小依然是C<B<A,所以可将B作为根节点,C为左子节点,A为右子节点。调整后该树保持平衡成为新的平衡二叉树。

从旋转的角度看,是失衡节点A经过了向右旋转,A节点下降成为B的右子节点,B结点上升,故该过程称为右旋。

同理,RR型,节点数值的大小是A<B<C,调整后失衡节点A经过了向左旋转,A节点下降成为B的左子节点,B结点上升,故该过程称为左旋。

LR型和RL型相对复杂点:

  • LR需要先左旋后右旋
  • RL需要先右旋后左旋

以下图LR型为例:
在这里插入图片描述来源于百度

(图:来源于百度)


3. 为什么有了平衡树还需要红黑树?


虽然平衡树解决了二叉查找树退化为近似链表的缺点,能够把查找时间控制在 O(logn),不过却不是最佳的,因为平衡树要求每个节点的左子树和右子树的高度差至多等于1,这个要求实在是太严了,导致每次进行插入/删除节点的时候,几乎都会破坏平衡树的第二个规则,进而我们都需要通过左旋和右旋来进行调整,使之再次成为一颗符合要求的平衡树。

显然,如果在那种插入、删除很频繁的场景中,平衡树需要频繁着进行调整,这会使平衡树的性能大打折扣,为了解决这个问题,于是有了红黑树!!!

最后以动图的方式来理解左旋和右旋,我们将在下一篇推文红黑树中再次重点讲解左旋右旋!

左旋:
在这里插入图片描述
右旋:
在这里插入图片描述

延申阅读: 红黑树核心:自平衡策略和插入算法是如何实现的?



参考资料:

小刘讲源码: https://www.bilibili.com/video/BV1UJ411J7CU?p=1
青岛大学王卓:https://www.bilibili.com/read/cv3285768

声明:

特别鸣谢B站博主-小刘讲源码,授权本文推送。本文未声明图片以及文案内容均来自于小刘老师。欢迎到B站小刘老师个人主页学习更多Hashmap、红黑树、CAS等源码!


欢迎关注公众号" Java全栈学习 ", 分享更多技术文章、书籍以及大厂面试宝典!

无套路分享Java架构师必读书籍, 请别吝啬一个赞!

链接:https://pan.baidu.com/s/10FbsqrgtewZBUls9SiAzzA
提取码:eued

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值