在上一章中,我们学习了二叉搜索树(BST)的基本概念和操作。虽然BST在平均情况下提供了O(log n)的搜索、插入和删除效率,但在最坏情况下(如按顺序插入数据),它可能退化为链表,导致操作效率降为O(n)。为了解决这个问题,《算法图解》第八章介绍了平衡树的概念和几种主要的平衡树结构,这些结构能够在各种情况下保持较好的平衡性,确保操作的高效性。
平衡树是一种特殊的二叉搜索树,它通过某些机制来保持树的平衡,避免树向一侧过度生长。所谓"平衡",通常是指树的左右子树高度大致相同,或者满足某些特定的平衡条件。
平衡树的目标是确保树的高度接近于log n(其中n是节点数),这样可以保证基本操作(查找、插入、删除)的时间复杂度为O(log n),即使在最坏情况下也是如此。
回顾上一章中提到的问题:当按顺序(如升序或降序)向BST中插入数据时,树会变得极度不平衡,形成一个链表状结构:
1
\
2
\
3
\
4
\
5
在这种情况下,查找、插入和删除操作的时间复杂度都退化为O(n),失去了BST的效率优势。平衡树通过在插入和删除操作中自动调整树的结构,确保树始终保持相对平衡,从而避免这种性能退化。
AVL树是最早被发明的自平衡二叉搜索树之一,由G.M. Adelson-Velsky和E.M. Landis在1962年提出(AVL就是取自他们名字的首字母)。
AVL树的关键特性是:
在AVL树中,每个节点的平衡因子定义为其左子树高度减去右子树高度的差值:
如果平衡因子的绝对值超过1,则树处于不平衡状态,需要通过旋转来恢复平衡。
AVL树通过四种基本的旋转操作来维持平衡:
这些旋转操作可以在O(1)时间内完成,因此不会显著影响插入和删除操作的效率。
优点:
缺点:
红黑树是另一种广泛使用的自平衡二叉搜索树,由Rudolf Bayer在1972年首次提出,后来由Leo J. Guibas和Robert Sedgewick完善并命名。红黑树不像AVL树那样严格平衡,但仍能保证O(log n)的操作效率,且插入和删除操作所需的旋转次数通常少于AVL树。
红黑树的每个节点都有一个颜色属性,可以是红色或黑色。红黑树必须满足以下五个性质:
这些性质共同确保了红黑树的关键特性:从根到最远叶节点的路径长度不会超过从根到最近叶节点的路径长度的两倍。这保证了红黑树的高度大致上是log n,因此所有基本操作的时间复杂度都是O(log n)。
红黑树通过颜色变换和旋转操作来维持平衡:
插入或删除节点后,如果违反了红黑树的任何性质,就会触发重新着色和/或旋转操作,以恢复这些性质。
优点:
缺点:
红黑树在实际系统和库中应用广泛:
虽然AVL树和红黑树主要用于内存中的数据结构,但当数据量大到无法全部加载到内存中时,我们需要考虑磁盘IO操作的效率。B树和B+树是专为外部存储(如磁盘)设计的平衡树结构。
B树(或B-树)是一种自平衡的多路搜索树,由Rudolf Bayer和Edward McCreight在1972年提出。与二叉搜索树不同,B树的每个节点可以有多个子节点,通常是设计成与磁盘页或系统块大小相匹配的结构。
B树的主要特性:
B树的设计使得即使处理大量数据,树的高度也能保持在较低水平,从而减少磁盘IO操作次数。
B+树是B树的一种变体,广泛用于数据库和文件系统中。B+树与B树的主要区别在于:
这些特性使B+树特别适合于数据库索引:
B树和B+树在大型数据存储系统中有广泛应用:
特性 | AVL树 | 红黑树 | B树 | B+树 |
---|---|---|---|---|
平衡条件 | 严格(高度差≤1) | 适中(黑色节点平衡) | 多路平衡 | 多路平衡+叶节点链表 |
查找性能 | O(log n),常数因子小 | O(log n),常数因子略大 | O(log n),基于块访问 | O(log n),基于块访问 |
插入/删除平衡开销 | 较高(可能多次旋转) | 适中(最多三次旋转) | 较低(分裂/合并操作) | 较低(分裂/合并操作) |
内存开销 | 每节点存储平衡因子 | 每节点存储颜色位 | 多键多指针 | 多键多指针+叶节点链表 |
主要应用 | 查询密集型内存数据结构 | 通用内存数据结构 | 外存数据组织 | 数据库索引 |
选择合适的平衡树结构应考虑以下因素:
数据规模:
操作频率:
实现复杂度:
具体应用场景:
以下是一个简单的AVL树实现示例:
class AVLNode:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.height = 1 # 新节点的初始高度为1
class AVLTree:
def get_height(self, node):
if not node:
return 0
return node.height
def get_balance(self, node):
if not node:
return 0
return self.get_height(node.left) - self.get_height(node.right)
def update_height(self, node):
if not node:
return
node.height = 1 + max(self.get_height(node.left), self.get_height(node.right))
def right_rotate(self, y):
x = y.left
T2 = x.right
# 执行旋转
x.right = y
y.left = T2
# 更新高度
self.update_height(y)
self.update_height(x)
return x
def left_rotate(self, x):
y = x.right
T2 = y.left
# 执行旋转
y.left = x
x.right = T2
# 更新高度
self.update_height(x)
self.update_height(y)
return y
def insert(self, root, key):
# 标准BST插入
if not root:
return AVLNode(key)
if key < root.key:
root.left = self.insert(root.left, key)
elif key > root.key:
root.right = self.insert(root.right, key)
else: # 相等的键不允许(或可以处理为计数器增加)
return root
# 更新高度
self.update_height(root)
# 获取平衡因子
balance = self.get_balance(root)
# 如果节点不平衡,则有四种情况
# 左左情况
if balance > 1 and key < root.left.key:
return self.right_rotate(root)
# 右右情况
if balance < -1 and key > root.right.key:
return self.left_rotate(root)
# 左右情况
if balance > 1 and key > root.left.key:
root.left = self.left_rotate(root.left)
return self.right_rotate(root)
# 右左情况
if balance < -1 and key < root.right.key:
root.right = self.right_rotate(root.right)
return self.left_rotate(root)
return root
def in_order_traversal(self, root):
result = []
if root:
result = self.in_order_traversal(root.left)
result.append(root.key)
result += self.in_order_traversal(root.right)
return result
# 使用示例
avl_tree = AVLTree()
root = None
# 插入节点
keys = [9, 5, 10, 0, 6, 11, -1, 1, 2]
for key in keys:
root = avl_tree.insert(root, key)
print("AVL树的中序遍历结果:", avl_tree.in_order_traversal(root))
平衡树是解决普通二叉搜索树在特定情况下性能退化问题的关键数据结构。不同类型的平衡树各有特点,适用于不同的应用场景:
掌握这些平衡树结构及其特性,对于理解和设计高效的数据处理系统至关重要。平衡树体现了计算机科学中平衡"理论最优"和"实际可行"的智慧,是算法设计中的经典案例。
本文由 mdnice 多平台发布