什么是二叉搜索树
二叉搜索树(Binary Search Tree,BST)是一种特殊类型的二叉树,它具有以下性质:
每个节点最多有两个子节点,分别称为左子节点和右子节点。
对于任意节点,其左子树中的所有节点的值都小于该节点的值。
对于任意节点,其右子树中的所有节点的值都大于该节点的值。
对于任意节点,其左子树和右子树也分别是二叉搜索树。
二叉搜索树特点
有序性:二叉搜索树中的节点按照一定的顺序排列,左子树的值小于根节点的值,右子树的值大于根节点的值。这使得在树中进行搜索、插入或删除操作时具有高效性能。
快速查找:由于有序性,可以利用二叉搜索树进行快速查找操作。通过比较目标值与当前节点的值,可以根据大小关系递归地在左子树或右子树中继续查找目标值。
插入和删除操作:由于二叉搜索树的有序性,插入和删除操作可以保持树的有序性。插入操作按照节点值的大小关系找到合适的位置插入新节点。删除操作需要维护树的有序性,并根据节点的情况进行相应的调整。
二叉搜索树的常见操作
查找(Search)
在二叉搜索树中查找给定值。从根节点开始,比较目标值与当前节点的值,根据大小关系递归地在左子树或右子树中查找,直到找到目标值或遍历到叶子节点。
以下实例在二分搜索树中寻找 43 元素
(1) 元素 43 比根节点 42 大,需要在右子节点继续比较。
(2) 元素 43 比 59 小,需要在左子节点继续比较。
(3) 元素 43 比 51 小,需要在左子节点继续比较。
(4) 查找 51 的左子节点 43,正好和相等,结束。
class TreeNode:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def search(root, target):
# 如果当前节点为None或者当前节点的值等于目标值,则返回当前节点
if root is None or root.val == target:
return root
# 如果目标值小于当前节点的值,则在左子树中查找
if target < root.val:
return search(root.left, target)
# 如果目标值大于当前节点的值,则在右子树中查找
else:
return search(root.right, target)
# 测试
# 构建二叉搜索树
root = TreeNode(5)
root.left = TreeNode(3)
root.right = TreeNode(7)
root.left.left = TreeNode(2)
root.left.right = TreeNode(4)
root.right.left = TreeNode(6)
root.right.right = TreeNode(8)
# 查找目标值为6的节点
result = search(root, 6)
if result:
print("查找成功,找到目标值为6的节点")
else:
print("查找失败,未找到目标值为6的节点")
插入(Insertion)
向二叉搜索树中插入新节点。按照节点值的大小关系,递归地在左子树或右子树中找到合适的位置插入新节点。
以下实例向如下二分搜索树中插入元素 61 的步骤:
(1)需要插入的元素 61 比 42 大,比较 42 的右子树根节点。
(2)61 比 59 大,所以需要把 61 移动到 59 右子树相应位置,而此时为空,直接插入作为 59 的右子节点。
插入操作也是一个递归过程,分三种情况,等于、大于、小于。
如果树为空,则将新节点作为根节点插入。
如果树不为空,则从根节点开始遍历:
如果新节点的值小于当前节点的值,并且当前节点的左子节点为空,则将新节点插入为当前节点的左子节点。
如果新节点的值大于当前节点的值,并且当前节点的右子节点为空,则将新节点插入为当前节点的右子节点。
如果新节点的值小于当前节点的值,并且当前节点的左子节点不为空,则将当前节点移动到其左子节点,并继续向下遍历。
如果新节点的值大于当前节点的值,并且当前节点的右子节点不为空,则将当前节点移动到其右子节点,并继续向下遍历。
class TreeNode:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def insert(root, val):
# 如果树为空,则新节点作为根节点
if not root:
return TreeNode(val)
# 如果新节点的值小于当前节点的值,则插入左子树
if val < root.val:
root.left = insert(root.left, val)
# 如果新节点的值大于等于当前节点的值,则插入右子树
else:
root.right = insert(root.right, val)
return root
# 测试
def inorder_traversal(root):
if root:
inorder_traversal(root.left)
print(root.val, end=' ')
inorder_traversal(root.right)
root = None
keys = [50, 30, 20, 40, 70, 60, 80]
for key in keys:
root = insert(root, key)
print("插入后的中序遍历结果:")
inorder_traversal(root)
删除(Deletion)
从二叉搜索树中删除节点。删除节点时,需要考虑节点的子树情况和维护树的有序性。常见的情况包括删除叶子节点、删除只有一个子节点的节点以及删除有两个子节点的节点。
1、删除只有左孩子的节点,如下图节点 58
删除掉元素 58,让左子树直接代替 58 的位置,整个二分搜索树的性质不变。
2、删除只有右孩子的节点,如下图节点 58
删除掉元素 58,让右子树直接代替 58 的位置,整个二分搜索树的性质不变。
3、删除左右都有孩子的节点,如下图节点 58
(1)找到右子树中的最小值,为节点 59
(2)节点 59 代替待删除节点 58
搜索要删除的节点:首先,我们需要在二叉搜索树中搜索要删除的节点。如果节点存在,我们就可以执行删除操作;如果节点不存在,则无需进行任何操作。
确定删除节点的类型:被删除的节点可能是叶子节点(没有子节点)、只有一个子节点的节点、或者有两个子节点的节点。
删除叶子节点:如果要删除的节点是叶子节点,只需将其父节点指向它的指针置为 None。
删除只有一个子节点的节点:如果要删除的节点只有一个子节点,我们可以将其父节点指向它的指针指向其唯一的子节点。
删除有两个子节点的节点:如果要删除的节点有两个子节点,我们需要找到它的后继节点(或者前驱节点),将其值替换到要删除的节点上,然后递归删除后继节点。
class TreeNode:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def delete_node(root, key):
if root is None:
return root
if key < root.val:
root.left = delete_node(root.left, key)
elif key > root.val:
root.right = delete_node(root.right, key)
else:
if root.left is None:
temp = root.right
root = None
return temp
elif root.right is None:
temp = root.left
root = None
return temp
temp = find_min(root.right)
root.val = temp.val
root.right = delete_node(root.right, temp.val)
return root
def find_min(node):
current = node
while current.left is not None:
current = current.left
return current
最小值和最大值(Minimum and Maximum)
最小值在树的最左边叶子节点上,最大值在树的最右边叶子节点上。
遍历(Traversal)
按照一定的顺序遍历二叉搜索树的所有节点。常见的遍历方式包括深度优先遍历和广度优先遍历。
前序遍历:
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def preorder_traversal(root):
if root is None:
return
print(root.value, end=" ") # 访问根节点
preorder_traversal(root.left) # 递归前序遍历左子树
preorder_traversal(root.right) # 递归前序遍历右子树
# 测试
# 构造一个二叉搜索树
root = TreeNode(5)
root.left = TreeNode(3)
root.right = TreeNode(8)
root.left.left = TreeNode(2)
root.left.right = TreeNode(4)
root.right.left = TreeNode(7)
root.right.right = TreeNode(9)
print("前序遍历结果:", end=" ")
preorder_traversal(root)
中序遍历:
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def inorder_traversal(root):
if root is None:
return []
result = []
stack = []
current = root
while current or stack:
while current:
stack.append(current)
current = current.left
current = stack.pop()
result.append(current.value)
current = current.right
return result
# 测试
root = TreeNode(5)
root.left = TreeNode(3)
root.right = TreeNode(8)
root.left.left = TreeNode(2)
root.left.right = TreeNode(4)
root.right.left = TreeNode(6)
root.right.right = TreeNode(9)
print("中序遍历结果:", inorder_traversal(root)) # 输出:[2, 3, 4, 5, 6, 8, 9]
后序遍历:
class TreeNode:
def __init__(self, value):
self.val = value
self.left = None
self.right = None
def postorder_traversal(root):
if root:
postorder_traversal(root.left)
postorder_traversal(root.right)
print(root.val, end=" ")
层序遍历:
通过引入一个队列来支撑层序遍历:
如果根节点为空,无可遍历;
如果根节点不为空:
先将根节点入队;
只要队列不为空:
出队队首节点,并遍历;
如果队首节点有左孩子,将左孩子入队;
如果队首节点有右孩子,将右孩子入队;
(1)先取出根节点放入队列
(2)取出 29,左右孩子节点入队
(3)队首 17 出队,孩子节点 14、23 入队。
(4)31 出队,孩子节点 30 和 43 入队
(5)最后全部出队
from collections import deque
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def level_order_traversal(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result
# 测试
# 构建一个二叉搜索树
root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20)
root.right.left = TreeNode(15)
root.right.right = TreeNode(7)
# 执行层序遍历
print("层序遍历结果:", level_order_traversal(root))