019-二叉搜索树
学习目标
通过本章学习,你将能够:
-
理解二叉搜索树的定义和特性
- 掌握二叉搜索树的基本概念
- 理解二叉搜索树的结构特点
- 了解二叉搜索树与普通二叉树的区别
-
掌握二叉搜索树的基本操作
- 实现查找、插入、删除操作
- 理解各种操作的算法原理
- 分析操作的时间复杂度
-
应用二叉搜索树解决实际问题
- 了解二叉搜索树的应用场景
- 掌握二叉搜索树的优化技巧
- 理解二叉搜索树的局限性
1. 二叉搜索树的定义与特性
1.1 基本定义
**二叉搜索树(Binary Search Tree,BST)**是一种特殊的二叉树,它满足以下性质:
- 左子树性质:对于任意节点,其左子树中所有节点的值都小于该节点的值
- 右子树性质:对于任意节点,其右子树中所有节点的值都大于该节点的值
- 递归性质:左右子树也都是二叉搜索树
- 唯一性:通常不允许重复值(或有特定的重复值处理策略)
1.2 数学定义
对于二叉搜索树中的任意节点 node
:
∀ left_node ∈ node.left_subtree: left_node.value < node.value
∀ right_node ∈ node.right_subtree: right_node.value > node.value
1.3 基本特征
class TreeNode:
"""二叉搜索树节点"""
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def __str__(self):
return str(self.val)
def __repr__(self):
return f"TreeNode({self.val})"
def is_valid_bst(root, min_val=float('-inf'), max_val=float('inf')):
"""验证是否为有效的二叉搜索树"""
if not root:
return True
# 检查当前节点是否满足BST性质
if root.val <= min_val or root.val >= max_val:
return False
# 递归检查左右子树
return (is_valid_bst(root.left, min_val, root.val) and
is_valid_bst(root.right, root.val, max_val))
# 示例:构建一个简单的BST
root = TreeNode(8)
root.left = TreeNode(3)
root.right = TreeNode(10)
root.left.left = TreeNode(1)
root.left.right = TreeNode(6)
root.right.right = TreeNode(14)
root.left.right.left = TreeNode(4)
root.left.right.right = TreeNode(7)
root.right.right.left = TreeNode(13)
print(f"是否为有效BST: {is_valid_bst(root)}") # True
# 验证BST的中序遍历结果是有序的
def inorder_traversal(root):
"""中序遍历"""
result = []
def inorder(node):
if node:
inorder(node.left)
result.append(node.val)
inorder(node.right)
inorder(root)
return result
print(f"中序遍历结果: {inorder_traversal(root)}") # [1, 3, 4, 6, 7, 8, 10, 13, 14]
1.4 重要性质
性质1:中序遍历有序性
- 对BST进行中序遍历,得到的序列是严格递增的
- 这是BST最重要的性质之一
性质2:查找效率
- 平均情况下,查找、插入、删除的时间复杂度为O(log n)
- 最坏情况下(退化为链表),时间复杂度为O(n)
性质3:动态性
- 支持动态插入和删除操作
- 结构会根据操作动态调整
2. 二叉搜索树的基本操作
2.1 查找操作
class BinarySearchTree:
"""二叉搜索树实现"""
def __init__(self):
self.root = None
self.size = 0
def search(self, val):
"""查找指定值的节点"""
return self._search_recursive(self.root, val)
def _search_recursive(self, node, val):
"""递归查找"""
# 基本情况:节点为空或找到目标值
if not node or node.val == val:
return node
# 根据BST性质决定搜索方向
if val < node.val:
return self._search_recursive(node.left, val)
else:
return self._search_recursive(node.right, val)
def search_iterative(self, val):
"""迭代查找"""
current = self.root
while current:
if val == current.val:
return current
elif val < current.val:
current = current.left
else:
current = current.right
return None
def contains(self, val):
"""检查是否包含指定值"""
return self.search(val) is not None
def find_min(self, node=None):
"""找到最小值节点"""
if node is None:
node = self.root
if not node:
return None
# 最小值在最左边
while node.left:
node = node.left
return node
def find_max(self, node=None):
"""找到最大值节点"""
if node is None:
node = self.root
if not node:
return None
# 最大值在最右边
while node.right:
node = node.right
return node
# 测试查找操作
bst = BinarySearchTree()
bst.root = root # 使用之前创建的树
print("\n=== 查找操作测试 ===")
print(f"查找6: {bst.search(6)}")
print(f"查找15: {bst.search(15)}")
print(f"包含7: {bst.contains(7)}")
print(f"包含15: {bst.contains(15)}")
print(f"最小值: {bst.find_min()}")
print(f"最大值: {bst.find_max()}")
2.2 插入操作
def insert(self, val):
"""插入新值"""
if not self.root:
self.root = TreeNode(val)
self.size += 1
return
self._insert_recursive(self.root, val)
def _insert_recursive(self, node, val):
"""递归插入"""
# 值已存在,不插入重复值
if val == node.val:
return
if val < node.val:
if node.left is None:
node.left = TreeNode(val)
self.size += 1
else:
self._insert_recursive(node.left, val)
else:
if node.right is None:
node.right = TreeNode(val)
self.size += 1
else:
self._insert_recursive(node.right, val)
def insert_iterative(self, val):
"""迭代插入"""
if not self.root:
self.root = TreeNode(val)
self.size += 1
return
current = self.root
parent = None
# 找到插入位置
while current:
parent = current
if val == current.val:
return # 值已存在
elif val < current.val:
current = current.left
else:
current = current.right
# 插入新节点
new_node = TreeNode(val)
if val < parent.val:
parent.left = new_node
else:
parent.right = new_node
self.size += 1
# 将方法添加到BST类
BinarySearchTree.insert = insert
BinarySearchTree._insert_recursive = _insert_recursive
BinarySearchTree.insert_iterative = insert_iterative
# 测试插入操作
print("\n=== 插入操作测试 ===")
bst_new = BinarySearchTree()
values = [8, 3, 10, 1, 6, 14, 4, 7, 13]
for val in values:
bst_new.insert(val)
print(f"插入 {val}")
print(f"中序遍历: {inorder_traversal(bst_new.root)}")
print(f"树的大小: {bst_new.size}")
# 尝试插入重复值
bst_new.insert(6)
print(f"插入重复值6后,树的大小: {bst_new.size}")
2.3 删除操作
删除操作是BST中最复杂的操作,需要考虑三种情况:
- 删除叶子节点:直接删除
- 删除只有一个子节点的节点:用子节点替换
- 删除有两个子节点的节点:用中序后继或前驱替换
def delete(self, val):
"""删除指定值的节点"""
self.root = self._delete_recursive(self.root, val)
def _delete_recursive(self, node, val):
"""递归删除"""
# 基本情况:节点为空
if not node:
return node
# 找到要删除的节点
if val < node.val:
node.left = self._delete_recursive(node.left, val)
elif val > node.val:
node.right = self._delete_recursive(node.right, val)
else:
# 找到要删除的节点
self.size -= 1
# 情况1:叶子节点或只有一个子节点
if not node.left:
return node.right
elif not node.right:
return node.left
# 情况2:有两个子节点
# 找到中序后继(右子树的最小值)
successor = self.find_min(node.right)
# 用后继的值替换当前节点的值
node.val = successor.val
# 删除后继节点
node.right = self._delete_recursive(node.right, successor.val)
self.size += 1 # 补偿上面的减1
return node
def find_successor(self, node):
"""找到节点的中序后继"""
if node.right:
return self.find_min(node.right)
# 如果没有右子树,需要向上查找
successor = None
current = self.root
while current:
if node.val < current.val:
successor = current
current = current.left
elif node.val > current.val:
current = current.right
else:
break
return successor
def find_predecessor(self, node):
"""找到节点的中序前驱"""
if node.left:
return self.find_max(node.left)
# 如果没有左子树,需要向上查找
predecessor = None
current = self.root
while current:
if node.val > current.val:
predecessor = current
current = current.right
elif node.val < current.val:
current = current.left
else:
break
return predecessor
# 将方法添加到BST类
BinarySearchTree.delete = delete
BinarySearchTree._delete_recursive = _delete_recursive
BinarySearchTree.find_successor = find_successor
BinarySearchTree.find_predecessor = find_predecessor
# 测试删除操作
print("\n=== 删除操作测试 ===")
bst_test = BinarySearchTree()
values = [8, 3, 10, 1, 6, 14, 4, 7, 13]
for val in values:
bst_test.insert(val)
print(f"删除前: {inorder_traversal(bst_test.root)}")
# 删除叶子节点
bst_test.delete(1)
print(f"删除1后: {inorder_traversal(bst_test.root)}")
# 删除只有一个子节点的节点
bst_test.delete(14)
print(f"删除14后: {inorder_traversal(bst_test.root)}")
# 删除有两个子节点的节点
bst_test.delete(3)
print(f"删除3后: {inorder_traversal(bst_test.root)}")
print(f"最终树的大小: {bst_test.size}")
3. 时间复杂度分析
3.1 平均情况分析
在平衡的BST中,树的高度约为 log₂(n),因此:
操作 | 时间复杂度 | 说明 |
---|---|---|
查找 | O(log n) | 每次比较可以排除一半的节点 |
插入 | O(log n) | 需要先查找插入位置 |
删除 | O(log n) | 需要查找节点并可能查找后继 |
最值查找 | O(log n) | 沿着一条路径到达叶子节点 |
3.2 最坏情况分析
当BST退化为链表时(如按顺序插入),树的高度为n:
操作 | 时间复杂度 | 说明 |
---|---|---|
查找 | O(n) | 需要遍历整条链 |
插入 | O(n) | 需要遍历到链的末端 |
删除 | O(n) | 需要遍历查找节点 |
最值查找 | O(n) | 需要遍历整条链 |
3.3 空间复杂度
def analyze_complexity():
"""分析BST的复杂度"""
def tree_height(node):
"""计算树的高度"""
if not node:
return 0
return 1 + max(tree_height(node.left), tree_height(node.right))
def count_nodes(node):
"""计算节点数量"""
if not node:
return 0
return 1 + count_nodes(node.left) + count_nodes(node.right)
# 创建平衡BST
balanced_bst = BinarySearchTree()
balanced_values = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]
for val in balanced_values:
balanced_bst.insert(val)
# 创建不平衡BST(链状)
unbalanced_bst = BinarySearchTree()
unbalanced_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for val in unbalanced_values:
unbalanced_bst.insert(val)
print("\n=== 复杂度分析 ===")
# 平衡树分析
balanced_nodes = count_nodes(balanced_bst.root)
balanced_height = tree_height(balanced_bst.root)
print(f"平衡BST - 节点数: {balanced_nodes}, 高度: {balanced_height}")
print(f"理论最优高度: {math.ceil(math.log2(balanced_nodes + 1))}")
# 不平衡树分析
unbalanced_nodes = count_nodes(unbalanced_bst.root)
unbalanced_height = tree_height(unbalanced_bst.root)
print(f"不平衡BST - 节点数: {unbalanced_nodes}, 高度: {unbalanced_height}")
print(f"最坏情况高度: {unbalanced_nodes}")
return balanced_bst, unbalanced_bst
import math
balanced_tree, unbalanced_tree = analyze_complexity()
4. 二叉搜索树的高级操作
4.1 范围查询
def range_search(self, min_val, max_val):
"""范围查询:找到指定范围内的所有值"""
result = []
def range_search_helper(node):
if not node:
return
# 如果当前节点值在范围内,加入结果
if min_val <= node.val <= max_val:
result.append(node.val)
# 根据BST性质决定是否需要搜索子树
if node.val > min_val:
range_search_helper(node.left)
if node.val < max_val:
range_search_helper(node.right)
range_search_helper(self.root)
return sorted(result)
def kth_smallest(self, k):
"""找到第k小的元素"""
def inorder_kth(node):
if not node:
return None
# 中序遍历,计数到k时返回
left_result = inorder_kth(node.left)
if left_result is not None:
return left_result
nonlocal count
count += 1
if count == k:
return node.val
return inorder_kth(node.right)
count = 0
return inorder_kth(self.root)
def kth_largest(self, k):
"""找到第k大的元素"""
def reverse_inorder_kth(node):
if not node:
return None
# 反向中序遍历(右-根-左)
right_result = reverse_inorder_kth(node.right)
if right_result is not None:
return right_result
nonlocal count
count += 1
if count == k:
return node.val
return reverse_inorder_kth(node.left)
count = 0
return reverse_inorder_kth(self.root)
# 将方法添加到BST类
BinarySearchTree.range_search = range_search
BinarySearchTree.kth_smallest = kth_smallest
BinarySearchTree.kth_largest = kth_largest
# 测试高级操作
print("\n=== 高级操作测试 ===")
bst_advanced = BinarySearchTree()
values = [8, 3, 10, 1, 6, 14, 4, 7, 13, 9, 11]
for val in values:
bst_advanced.insert(val)
print(f"树中所有值: {inorder_traversal(bst_advanced.root)}")
print(f"范围查询[5, 12]: {bst_advanced.range_search(5, 12)}")
print(f"第3小的元素: {bst_advanced.kth_smallest(3)}")
print(f"第3大的元素: {bst_advanced.kth_largest(3)}")
4.2 树的验证和统计
def validate_bst(self):
"""验证BST的有效性"""
return is_valid_bst(self.root)
def get_statistics(self):
"""获取树的统计信息"""
def calculate_stats(node):
if not node:
return {
'count': 0,
'height': 0,
'sum': 0,
'min': float('inf'),
'max': float('-inf')
}
left_stats = calculate_stats(node.left)
right_stats = calculate_stats(node.right)
return {
'count': 1 + left_stats['count'] + right_stats['count'],
'height': 1 + max(left_stats['height'], right_stats['height']),
'sum': node.val + left_stats['sum'] + right_stats['sum'],
'min': min(node.val, left_stats['min'], right_stats['min']),
'max': max(node.val, left_stats['max'], right_stats['max'])
}
stats = calculate_stats(self.root)
if stats['count'] > 0:
stats['average'] = stats['sum'] / stats['count']
stats['balance_factor'] = stats['height'] / math.log2(stats['count'] + 1)
else:
stats['average'] = 0
stats['balance_factor'] = 0
return stats
def is_balanced(self, threshold=2.0):
"""检查树是否相对平衡"""
stats = self.get_statistics()
return stats['balance_factor'] <= threshold
# 将方法添加到BST类
BinarySearchTree.validate_bst = validate_bst
BinarySearchTree.get_statistics = get_statistics
BinarySearchTree.is_balanced = is_balanced
# 测试验证和统计
print("\n=== 验证和统计测试 ===")
stats = bst_advanced.get_statistics()
print(f"BST有效性: {bst_advanced.validate_bst()}")
print(f"节点数量: {stats['count']}")
print(f"树高度: {stats['height']}")
print(f"数值总和: {stats['sum']}")
print(f"平均值: {stats['average']:.2f}")
print(f"最小值: {stats['min']}")
print(f"最大值: {stats['max']}")
print(f"平衡因子: {stats['balance_factor']:.2f}")
print(f"是否平衡: {bst_advanced.is_balanced()}")
5. 二叉搜索树的应用场景
5.1 数据库索引
class DatabaseIndex:
"""模拟数据库索引"""
def __init__(self):
self.bst = BinarySearchTree()
self.records = {} # 存储实际记录
def insert_record(self, key, record):
"""插入记录"""
self.bst.insert(key)
self.records[key] = record
def find_record(self, key):
"""查找记录"""
if self.bst.contains(key):
return self.records[key]
return None
def range_query(self, min_key, max_key):
"""范围查询"""
keys = self.bst.range_search(min_key, max_key)
return [(key, self.records[key]) for key in keys]
def delete_record(self, key):
"""删除记录"""
if self.bst.contains(key):
self.bst.delete(key)
del self.records[key]
return True
return False
# 测试数据库索引
print("\n=== 数据库索引应用 ===")
db_index = DatabaseIndex()
# 插入员工记录
employees = [
(1001, {'name': '张三', 'department': '技术部', 'salary': 8000}),
(1003, {'name': '李四', 'department': '销售部', 'salary': 6000}),
(1002, {'name': '王五', 'department': '技术部', 'salary': 9000}),
(1005, {'name': '赵六', 'department': '人事部', 'salary': 7000}),
(1004, {'name': '钱七', 'department': '财务部', 'salary': 7500})
]
for emp_id, emp_data in employees:
db_index.insert_record(emp_id, emp_data)
# 查询操作
print(f"查找员工1003: {db_index.find_record(1003)}")
print(f"员工ID范围[1002, 1004]: {db_index.range_query(1002, 1004)}")
# 删除操作
db_index.delete_record(1005)
print(f"删除1005后,查找1005: {db_index.find_record(1005)}")
5.2 表达式解析
class ExpressionParser:
"""表达式解析器"""
def __init__(self):
self.variables = BinarySearchTree()
self.var_values = {}
def set_variable(self, name, value):
"""设置变量值"""
var_hash = hash(name) % 10000 # 简单哈希
self.variables.insert(var_hash)
self.var_values[var_hash] = (name, value)
def get_variable(self, name):
"""获取变量值"""
var_hash = hash(name) % 10000
if self.variables.contains(var_hash):
stored_name, value = self.var_values[var_hash]
if stored_name == name:
return value
return None
def list_variables(self):
"""列出所有变量"""
result = []
for var_hash, (name, value) in self.var_values.items():
if self.variables.contains(var_hash):
result.append((name, value))
return sorted(result)
# 测试表达式解析
print("\n=== 表达式解析应用 ===")
parser = ExpressionParser()
# 设置变量
variables = [('x', 10), ('y', 20), ('z', 30), ('a', 5), ('b', 15)]
for name, value in variables:
parser.set_variable(name, value)
print(f"变量x的值: {parser.get_variable('x')}")
print(f"变量w的值: {parser.get_variable('w')}")
print(f"所有变量: {parser.list_variables()}")
5.3 优先级队列
class PriorityQueue:
"""基于BST的优先级队列"""
def __init__(self):
self.bst = BinarySearchTree()
self.items = {} # priority -> [items]
self.counter = 0 # 用于处理相同优先级
def enqueue(self, item, priority):
"""入队"""
# 使用计数器确保唯一性
unique_priority = priority * 10000 + self.counter
self.counter += 1
self.bst.insert(unique_priority)
if unique_priority not in self.items:
self.items[unique_priority] = []
self.items[unique_priority].append(item)
def dequeue(self):
"""出队(最高优先级)"""
if self.is_empty():
return None
# 找到最大优先级
max_node = self.bst.find_max()
max_priority = max_node.val
# 取出项目
items = self.items[max_priority]
item = items.pop(0)
# 如果该优先级没有更多项目,删除节点
if not items:
self.bst.delete(max_priority)
del self.items[max_priority]
return item
def peek(self):
"""查看最高优先级项目"""
if self.is_empty():
return None
max_node = self.bst.find_max()
max_priority = max_node.val
return self.items[max_priority][0]
def is_empty(self):
"""检查是否为空"""
return self.bst.root is None
def size(self):
"""获取大小"""
return sum(len(items) for items in self.items.values())
# 测试优先级队列
print("\n=== 优先级队列应用 ===")
pq = PriorityQueue()
# 添加任务
tasks = [
('紧急修复', 9),
('日常维护', 3),
('功能开发', 5),
('代码审查', 4),
('系统升级', 8),
('文档更新', 2)
]
for task, priority in tasks:
pq.enqueue(task, priority)
print(f"添加任务: {task} (优先级: {priority})")
print(f"\n队列大小: {pq.size()}")
print(f"最高优先级任务: {pq.peek()}")
# 处理任务
print("\n处理任务:")
while not pq.is_empty():
task = pq.dequeue()
print(f"处理: {task}")
6. 软考中二叉搜索树的考点
6.1 主要考点
1. 基本概念理解
- BST的定义和性质
- BST与普通二叉树的区别
- 中序遍历的有序性
2. 基本操作实现
- 查找、插入、删除算法
- 递归和迭代实现
- 边界情况处理
3. 复杂度分析
- 平均和最坏情况的时间复杂度
- 空间复杂度分析
- 平衡性对性能的影响
4. 应用场景
- 数据库索引
- 符号表实现
- 优先级队列
6.2 典型例题
例题1:BST验证
题目:给定一个二叉树,判断它是否是有效的二叉搜索树。
示例1:
2
/ \
1 3
输出:true
示例2:
5
/ \
1 4
/ \
3 6
输出:false(因为3 < 5但在右子树中)
解答思路:
- 使用递归方法,为每个节点维护取值范围
- 根节点的范围是(-∞, +∞)
- 左子树的范围是(min, node.val),右子树的范围是(node.val, max)
例题2:BST中第k小元素
题目:给定BST的根节点和整数k,返回树中第k小的元素。
解答思路:
- 利用BST中序遍历有序的性质
- 进行中序遍历,计数到第k个元素时返回
- 可以优化为提前终止的遍历
例题3:BST的范围和
题目:给定BST的根节点和范围[low, high],返回范围内所有节点值的和。
解答思路:
- 利用BST的性质进行剪枝
- 如果节点值小于low,只需搜索右子树
- 如果节点值大于high,只需搜索左子树
- 如果节点值在范围内,累加并搜索两个子树
6.3 解题技巧
1. 利用BST性质
- 中序遍历的有序性
- 左小右大的结构特点
- 递归子结构的性质
2. 边界条件处理
- 空树的处理
- 单节点树的处理
- 重复值的处理策略
3. 复杂度优化
- 利用BST性质进行剪枝
- 避免不必要的子树遍历
- 选择合适的遍历方式
4. 常见错误避免
- 忘记检查BST的有效性
- 删除操作中的指针处理错误
- 递归终止条件设置不当
7. 总结
7.1 核心要点
1. BST的本质
- 有序的二叉树结构
- 支持高效的动态操作
- 平衡性决定性能
2. 基本操作
- 查找:O(log n) 平均,O(n) 最坏
- 插入:O(log n) 平均,O(n) 最坏
- 删除:O(log n) 平均,O(n) 最坏
3. 关键性质
- 中序遍历产生有序序列
- 左子树 < 根 < 右子树
- 递归结构特性
7.2 应用指南
1. 适用场景
- 需要维护有序数据集合
- 频繁的查找、插入、删除操作
- 范围查询需求
- 动态数据集合管理
2. 选择考虑
- 数据的插入模式(是否有序)
- 对平衡性的要求
- 性能需求和约束
- 实现复杂度要求
3. 优化策略
- 考虑使用自平衡BST(如AVL、红黑树)
- 批量操作的优化
- 缓存常用查询结果
7.3 软考重点
1. 理论掌握
- BST的定义和性质
- 各种操作的算法原理
- 时间和空间复杂度
2. 实现能力
- 能够实现基本操作
- 处理各种边界情况
- 选择合适的实现方式
3. 应用理解
- 了解BST的应用场景
- 能够分析性能特点
- 知道何时选择BST
8. 实践练习
练习1:BST迭代器
实现一个BST迭代器,支持:
next()
:返回下一个最小元素hasNext()
:检查是否还有下一个元素- 要求:平均时间复杂度O(1),空间复杂度O(h)
练习2:BST序列化
实现BST的序列化和反序列化:
- 将BST序列化为字符串
- 从字符串反序列化为BST
- 要求:保持BST的结构特性
练习3:BST转换
实现以下转换功能:
- 将有序数组转换为平衡BST
- 将BST转换为有序双向链表
- 将BST转换为更平衡的BST
练习4:BST应用
设计一个基于BST的学生成绩管理系统:
- 支持按学号查找学生
- 支持按成绩范围查询
- 支持统计功能(平均分、排名等)
- 支持动态添加和删除学生
9. 下一步学习
学习完本章后,建议继续学习:
- 020-平衡二叉树AVL:学习自平衡BST的实现
- 红黑树:另一种重要的自平衡BST
- B树和B+树:多路搜索树,数据库索引的基础
- 字典树(Trie):字符串搜索的专用树结构
- 线段树:支持区间查询的树结构
通过本章的学习,你应该能够熟练掌握二叉搜索树的原理和实现,理解其在各种应用场景中的作用,并能在软考中正确回答相关问题。记住,BST是许多高级数据结构的基础,掌握好BST对后续学习非常重要。