数据结构——平衡二叉树

平衡二叉树

在二叉搜索树的使用过程中,我们发现一个严重的问题会影响查找效率。当向二叉搜索树中依次插入一组有序或接近有序的数据时,例如依次插入1、2、3、4、5,得到的二叉搜索树会退化成一条单链,此时查找的时间复杂度从理想的O(log₂n)退化为O(n)。为了解决这个问题,需要在构造二叉搜索树的过程中保持树的平衡性,使得树的高度尽可能小,从而保证查找效率。平衡二叉树正是为解决这一问题而提出的一种特殊的二叉搜索树。

1. 平衡二叉树的定义

平衡二叉树(Balanced Binary Tree),又称AVL树,是一种经过改进的二叉搜索树。它由两位苏联数学家G.M. Adelson-Velsky和E.M. Landis在1962年提出,AVL这个名称也是取自两位发明者名字的首字母。

平衡二叉树在满足二叉搜索树所有性质的基础上,增加了平衡性的约束。具体来说,平衡二叉树或者是一棵空树,或者是具有如下性质的二叉搜索树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的高度之差的绝对值不超过1。我们将二叉树上任意节点的左子树与右子树的高度差称为该节点的平衡因子(Balance Factor)。因此,平衡二叉树中所有节点的平衡因子只可能是-1、0或1。若某节点的平衡因子的绝对值大于1,则该二叉树就不是平衡二叉树。

为了更直观地理解平衡二叉树的概念,我们通过具体例子来说明。
50
BF=0 30
BF=0 70
BF=0 20
BF=0 40
BF=0 60
BF=0 80
BF=0

上图展示了一棵平衡二叉树,图中每个节点下方标注了BF(Balance Factor)值。根节点50的左子树高度为2,右子树高度也为2,平衡因子为0。节点30的左右子树高度均为1,平衡因子为0。可以看到,树中所有节点的平衡因子都在-1到1之间,符合平衡二叉树的定义。

与之对比,下面展示一棵不平衡的二叉搜索树。
50
BF=-2 30
BF=0 70
BF=-2 80
BF=-1 90
BF=-1 100
BF=0

在这棵树中,节点50的平衡因子为-2,节点70的平衡因子也为-2,它们的平衡因子绝对值超过了1,因此这不是一棵平衡二叉树。这种结构会导致右侧子树过深,使得查找效率下降。

在平衡二叉树中,节点的平衡因子通常定义为左子树高度减去右子树高度。当平衡因子为正时,表示左子树较高;为负时,表示右子树较高;为0时,表示左右子树等高。这个定义在不同的教材中可能有所不同,有些定义为右子树高度减去左子树高度,但无论采用哪种定义方式,其本质是一致的,都是为了量化左右子树的高度差。

2. 平衡二叉树的性质

平衡二叉树具有一些重要的性质,这些性质保证了平衡二叉树能够维持较好的查找性能。

首先,平衡二叉树的高度是对数级别的。设含有n个节点的平衡二叉树的最大高度为h,可以证明h=O(log₂n)。这意味着即使数据规模增长,树的高度增长也是缓慢的。具体来说,高度为h的平衡二叉树,其节点数至少为斐波那契数列的第h+3项减1。因此,n个节点的平衡二叉树的最大深度约为1.44log₂(n+2)-1.328,这个结果说明平衡二叉树的高度与完全二叉树的高度是同一数量级的。

其次,平衡二叉树的查找、插入和删除操作的时间复杂度均为O(log₂n)。由于树的高度被限制在对数级别,从根节点到任意叶节点的路径长度都不会太长,因此沿着路径进行的查找操作能够在对数时间内完成。插入和删除操作虽然可能需要调整树的结构以维持平衡性,但调整的次数也是有限的,总体时间复杂度仍然保持在O(log₂n)。

第三,平衡二叉树的平衡调整是局部的。当向平衡二叉树中插入或删除节点导致某个节点失衡时,只需要对失衡节点及其子树进行调整,不需要调整整棵树。这种局部调整的特性使得平衡维护的开销相对较小,也是平衡二叉树实用性的重要保证。

3. 平衡二叉树的插入操作

在平衡二叉树中插入新节点时,首先按照二叉搜索树的插入方法找到插入位置并插入新节点,然后从插入位置沿着路径向上检查各节点的平衡因子。如果发现某个节点的平衡因子的绝对值大于1,说明该节点失衡,需要通过旋转操作来恢复平衡。

根据失衡节点的平衡因子以及其子节点的平衡因子,失衡的情况可以分为四种类型。这四种类型分别对应不同的旋转调整方式。

(1)LL型失衡(左左型)

LL型失衡是指在节点的左子树的左子树上插入新节点,导致该节点的平衡因子变为2。此时需要对该节点进行右旋操作来恢复平衡。
右旋后恢复平衡 插入5后失衡 插入前平衡 10
BF=-1 20
BF=0 30
BF=-1 5
BF=0 40
BF=0 20
BF=1 30
BF=2 40
BF=0 10
BF=-1 5
BF=0 20
BF=0 30
BF=1 40
BF=0 10
BF=0

上图展示了LL型失衡及其调整过程。初始时,树是平衡的。在节点10的左子树插入节点5后,节点30的平衡因子变为2,出现失衡。通过对节点30进行右旋操作,使节点20成为新的根节点,节点30变为节点20的右子节点,最终恢复平衡。

(2)RR型失衡(右右型)

RR型失衡是指在节点的右子树的右子树上插入新节点,导致该节点的平衡因子变为-2。此时需要对该节点进行左旋操作来恢复平衡。
左旋后恢复平衡 插入60后失衡 插入前平衡 30
BF=1 40
BF=0 50
BF=-1 20
BF=0 60
BF=0 20
BF=0 30
BF=-2 40
BF=-1 50
BF=-1 60
BF=0 20
BF=0 30
BF=-1 40
BF=0 50
BF=0

上图展示了RR型失衡及其调整过程。在节点50的右子树插入节点60后,节点30的平衡因子变为-2。通过对节点30进行左旋操作,使节点40成为新的根节点,节点30变为节点40的左子节点,树恢复平衡。

(3)LR型失衡(左右型)

LR型失衡是指在节点的左子树的右子树上插入新节点,导致该节点的平衡因子变为2。这种情况需要进行两次旋转:先对失衡节点的左子节点进行左旋,再对失衡节点进行右旋。
再右旋节点30 先左旋节点20 插入15后失衡 插入前平衡 10
BF=0 15
BF=0 30
BF=-1 20
BF=0 40
BF=0 15
BF=1 30
BF=2 40
BF=0 10
BF=0 20
BF=0 20
BF=-1 30
BF=2 40
BF=0 10
BF=1 15
BF=0 20
BF=0 30
BF=1 40
BF=0 10
BF=0

上图详细展示了LR型失衡的双旋转调整过程。在节点10的右子树插入节点15后,节点30出现失衡。首先对节点20进行左旋,使节点15上升,节点20下降成为其右子节点;然后对节点30进行右旋,使节点15成为整棵子树的根节点,最终树恢复平衡。

(4)RL型失衡(右左型)

RL型失衡是指在节点的右子树的左子树上插入新节点,导致该节点的平衡因子变为-2。这种情况也需要进行两次旋转:先对失衡节点的右子节点进行右旋,再对失衡节点进行左旋。
再左旋节点30 先右旋节点50 插入55后失衡 插入前平衡 30
BF=1 55
BF=0 60
BF=0 20
BF=0 50
BF=0 20
BF=0 30
BF=-2 55
BF=-1 50
BF=0 60
BF=0 20
BF=0 30
BF=-2 50
BF=1 60
BF=-1 55
BF=0 20
BF=0 30
BF=-1 50
BF=0 60
BF=0

上图展示了RL型失衡的双旋转调整过程。在节点60的左子树插入节点55后,节点30的平衡因子变为-2。首先对节点50进行右旋,使节点55上升;然后对节点30进行左旋,使节点55成为子树的根节点,树恢复平衡。

通过以上四种旋转操作,可以处理插入节点时出现的所有失衡情况。需要注意的是,在调整过程中,从插入点到根节点的路径上可能有多个节点的平衡因子发生变化,但只需要调整最靠近插入点的那个失衡节点即可,调整后整棵树就能恢复平衡,这体现了平衡二叉树调整的局部性特征。

4. 平衡二叉树的删除操作

平衡二叉树的删除操作相对插入操作更为复杂。删除节点的基本过程与二叉搜索树相同,但删除后可能导致树失衡,需要进行相应的调整。删除操作可能引起从删除位置到根节点路径上多个节点的失衡,需要逐层向上检查并调整。

删除节点的具体步骤可以归纳如下。首先,按照二叉搜索树的删除规则删除目标节点。如果删除的是叶节点,直接删除;如果删除的节点只有一个子树,用其子树替代;如果删除的节点有两个子树,则找到其中序后继(或中序前驱)节点,用该节点的值替换被删除节点的值,然后删除该后继节点。

删除节点后,需要从删除位置开始向上回溯,检查路径上每个节点的平衡因子。一旦发现失衡节点,就需要根据失衡类型进行相应的旋转调整。与插入操作不同的是,删除操作调整一个失衡节点后,可能导致其父节点也失衡,因此需要继续向上检查调整,直到根节点或者确认所有节点都平衡为止。
调整后恢复平衡 删除节点20后失衡 删除前的平衡二叉树 40
BF=0 50
BF=0 70
BF=0 30
BF=0 45
BF=0 60
BF=0 80
BF=0 30
BF=-2 50
BF=-1 70
BF=0 40
BF=-1 45
BF=0 60
BF=0 80
BF=0 30
BF=-1 50
BF=0 70
BF=0 20
BF=0 40
BF=-1 45
BF=0 60
BF=0 80
BF=0

上图演示了删除操作引起失衡的情况。初始树是平衡的,删除节点20后,节点30的平衡因子变为-2,属于RL型失衡。通过先右旋节点40再左旋节点30,最终树恢复平衡。这个例子说明了删除操作可能需要进行双旋转来恢复平衡。

在某些情况下,删除一个节点并调整后,可能会使得父节点的高度减小,进而导致祖先节点失衡,需要继续向上调整。这种连锁反应最多会延续到根节点,但实际应用中,大多数删除操作只需要进行有限次数的调整。

5. 平衡二叉树的查找操作

平衡二叉树的查找操作与普通二叉搜索树完全相同,充分利用了二叉搜索树的有序性。从根节点开始,将待查找的关键字与当前节点的关键字进行比较。如果相等,则查找成功;如果待查关键字小于当前节点关键字,则在左子树中继续查找;如果大于当前节点关键字,则在右子树中继续查找。若查找到叶节点仍未找到,则查找失败。
45 < 50 45 > 50 45 > 30 45 < 30 45 > 40 45 < 40 45 = 45 查找值=45 当前节点50 当前节点30 访问右子树 当前节点40 访问左子树 当前节点45 访问左子树 查找成功

上图展示了在平衡二叉树中查找值为45的过程。从根节点50开始,因为45小于50,转向左子树;到达节点30,因为45大于30,转向右子树;到达节点40,因为45大于40,继续向右;最终在节点45处找到目标值,查找成功。整个查找过程沿着一条从根到目标节点的路径进行,访问的节点数不超过树的高度。

由于平衡二叉树的高度保持在O(log₂n)级别,查找操作的时间复杂度也为O(log₂n)。相比退化为单链的二叉搜索树,平衡二叉树的查找效率有显著提升。对于包含n个节点的平衡二叉树,平均查找长度约为log₂n,即使在最坏情况下也能保持这一性能,这正是平衡二叉树的核心优势所在。

6. 平衡二叉树的性能分析

平衡二叉树通过限制树的高度来保证操作效率,其性能特点体现在多个方面。

从时间复杂度角度来看,平衡二叉树的查找、插入、删除操作的时间复杂度均为O(log₂n)。这个性能保证来源于树高度的限制。无论数据如何插入,平衡调整机制都能确保树的高度不会超过1.44log₂(n+2)。因此,即使在最坏情况下,这些基本操作也能在对数时间内完成,具有良好的稳定性。

从空间复杂度角度来看,每个节点除了存储数据和左右子节点指针外,还需要存储平衡因子或节点高度信息,空间开销相对普通二叉搜索树略有增加。但这个额外开销通常只需要一个字节或整数,相对于节点本身的数据,这个代价是可以接受的。

平衡二叉树的维护成本主要体现在插入和删除操作中的旋转调整。插入操作最多需要一次单旋转或一次双旋转,调整次数是常数级的。删除操作在最坏情况下可能需要O(log₂n)次旋转,因为可能需要沿着路径向上逐层调整。尽管如此,实际应用中大多数删除操作只需要很少的调整次数。

与普通二叉搜索树相比,平衡二叉树牺牲了部分插入和删除的简单性,换取了稳定的查找性能。在查找操作频繁、对查找效率要求较高的应用场景中,平衡二叉树具有明显优势。但如果数据本身比较随机,普通二叉搜索树就能保持较好的平衡性,此时使用平衡二叉树的收益可能不明显。因此,在实际应用中需要根据具体的数据特征和操作模式来选择合适的数据结构。

平衡二叉树作为一种自平衡的二叉搜索树,为后续更复杂的平衡树结构(如红黑树、B树等)奠定了理论基础。虽然在实际系统中,红黑树因其更宽松的平衡条件和更少的旋转次数而被更广泛地使用,但平衡二叉树的思想和原理仍然具有重要的学习价值,它清晰地展示了如何通过局部调整来维护全局性质,这种思想在算法设计中具有普遍意义。

相关推荐
py有趣3 小时前
LeetCode算法学习之合并区间
学习·算法·leetcode
m0_748233643 小时前
单调栈详解【C/C++】
c语言·c++·算法·1024程序员节
郝学胜-神的一滴4 小时前
Linux中的`fork`函数详解:深入解析
linux·服务器·开发语言·c++·算法
大数据张老师4 小时前
数据结构——BF算法
数据结构·算法·1024程序员节
让我们一起加油好吗5 小时前
【数论】欧拉定理 && 扩展欧拉定理
c++·算法·数论·1024程序员节·欧拉定理·欧拉降幂·扩展欧拉定理
Yupureki5 小时前
从零开始的C++学习生活 14:map/set的使用和封装
c语言·数据结构·c++·学习·visual studio·1024程序员节
一匹电信狗5 小时前
【LeetCode_876_2.02】快慢指针在链表中的简单应用
c语言·数据结构·c++·算法·leetcode·链表·stl
胖咕噜的稞达鸭5 小时前
算法入门---专题二:滑动窗口2(最大连续1的个数,无重复字符的最长子串 )
c语言·数据结构·c++·算法·推荐算法·1024程序员节
兮山与5 小时前
算法18.0
算法