手写AVL树(平衡二叉搜索树)的实现
字数 1321 2025-12-05 18:41:45

手写AVL树(平衡二叉搜索树)的实现

AVL树是一种自平衡的二叉搜索树,在插入和删除操作后,能通过旋转操作自动调整树的结构,使树保持平衡,从而保证查找、插入、删除操作的时间复杂度均为O(log n)。它的核心特性是:任何节点的左右子树的高度差(平衡因子)的绝对值不超过1

一、核心概念与定义

  1. 平衡因子:对于任意节点,其平衡因子 = 左子树高度 - 右子树高度。在AVL树中,平衡因子的值只能是-1、0、1。
  2. 旋转操作:当插入或删除节点导致树不平衡时(即某个节点的平衡因子变为2或-2),需要通过旋转来恢复平衡。旋转分为四种基本类型。

二、节点结构

我们先定义树节点,它除了存储键值、左右子节点指针外,还需要存储当前节点的高度(或平衡因子)。

class AVLNode:
    def __init__(self, key):
        self.key = key
        self.left = None
        self.right = None
        self.height = 1  # 节点初始高度为1

三、辅助函数

实现几个辅助函数,用于计算高度、更新高度、计算平衡因子。

def get_height(node):
    if not node:
        return 0
    return node.height

def get_balance_factor(node):
    if not node:
        return 0
    return get_height(node.left) - get_height(node.right)

def update_height(node):
    if not node:
        return
    node.height = 1 + max(get_height(node.left), get_height(node.right))

四、旋转操作

旋转操作是AVL树实现平衡的核心。根据不平衡节点的平衡因子以及其较高子树(导致不平衡的那一侧子树)的平衡因子,分为四种情况。

1. 左旋(LL型)
当节点A的平衡因子为-2,且其右子节点B的平衡因子为-1或0时,进行左旋。

  • 情况描述:在A的右子树的右侧插入了新节点,导致A不平衡。
  • 操作:以A的右子节点B为支点,将A向左旋转下来,使B成为新的根节点。
def left_rotate(z):
    y = z.right
    T2 = y.left
    # 执行旋转
    y.left = z
    z.right = T2
    # 更新高度
    update_height(z)
    update_height(y)
    return y

2. 右旋(RR型)
当节点A的平衡因子为2,且其左子节点B的平衡因子为1或0时,进行右旋。

  • 情况描述:在A的左子树的左侧插入了新节点,导致A不平衡。
  • 操作:以A的左子节点B为支点,将A向右旋转下来,使B成为新的根节点。
def right_rotate(z):
    y = z.left
    T3 = y.right
    # 执行旋转
    y.right = z
    z.left = T3
    # 更新高度
    update_height(z)
    update_height(y)
    return y

3. 左右旋(LR型)
当节点A的平衡因子为2,且其左子节点B的平衡因子为-1时,先对B进行左旋,再对A进行右旋。

  • 情况描述:在A的左子树的右侧插入了新节点。
  • 操作:先左旋B,将情况转换为RR型,再右旋A。
def left_right_rotate(z):
    z.left = left_rotate(z.left)
    return right_rotate(z)

4. 右左旋(RL型)
当节点A的平衡因子为-2,且其右子节点B的平衡因子为1时,先对B进行右旋,再对A进行左旋。

  • 情况描述:在A的右子树的左侧插入了新节点。
  • 操作:先右旋B,将情况转换为LL型,再左旋A。
def right_left_rotate(z):
    z.right = right_rotate(z.right)
    return left_rotate(z)

五、插入操作

插入操作与BST插入类似,但插入后需要从插入点向上回溯,更新祖先节点的高度,并检查平衡因子,如果不平衡则进行相应的旋转。

def insert(root, key):
    # 1. 执行标准的BST插入
    if not root:
        return AVLNode(key)
    if key < root.key:
        root.left = insert(root.left, key)
    elif key > root.key:
        root.right = insert(root.right, key)
    else:
        return root  # 重复键不插入

    # 2. 更新当前节点的高度
    update_height(root)

    # 3. 获取当前节点的平衡因子
    balance = get_balance_factor(root)

    # 4. 根据平衡因子进行旋转调整
    # 左左情况
    if balance > 1 and key < root.left.key:
        return right_rotate(root)
    # 右右情况
    if balance < -1 and key > root.right.key:
        return left_rotate(root)
    # 左右情况
    if balance > 1 and key > root.left.key:
        return left_right_rotate(root)
    # 右左情况
    if balance < -1 and key < root.right.key:
        return right_left_rotate(root)

    return root

六、删除操作

删除操作也与BST删除类似,但删除后同样需要回溯调整平衡。删除有三种情况:

  1. 要删除的节点是叶子节点:直接删除。
  2. 要删除的节点只有一个子节点:用子节点替换它。
  3. 要删除的节点有两个子节点:找到其右子树中的最小节点(或左子树中的最大节点),用这个最小节点的键值替换要删除节点的键值,然后递归删除那个最小节点。

删除后,同样需要更新高度并检查平衡因子,进行必要的旋转。

def delete(root, key):
    if not root:
        return root
    # 1. 执行标准的BST删除
    if key < root.key:
        root.left = delete(root.left, key)
    elif key > root.key:
        root.right = delete(root.right, key)
    else:
        # 节点有一个子节点或没有子节点
        if not root.left:
            return root.right
        elif not root.right:
            return root.left
        # 节点有两个子节点:获取右子树的最小节点
        temp = get_min_node(root.right)
        root.key = temp.key
        # 删除右子树的最小节点
        root.right = delete(root.right, temp.key)

    # 如果树只有一个节点,删除后返回
    if not root:
        return root

    # 2. 更新当前节点的高度
    update_height(root)

    # 3. 获取当前节点的平衡因子
    balance = get_balance_factor(root)

    # 4. 根据平衡因子进行旋转调整
    # 左左情况
    if balance > 1 and get_balance_factor(root.left) >= 0:
        return right_rotate(root)
    # 左右情况
    if balance > 1 and get_balance_factor(root.left) < 0:
        return left_right_rotate(root)
    # 右右情况
    if balance < -1 and get_balance_factor(root.right) <= 0:
        return left_rotate(root)
    # 右左情况
    if balance < -1 and get_balance_factor(root.right) > 0:
        return right_left_rotate(root)

    return root

def get_min_node(node):
    current = node
    while current.left:
        current = current.left
    return current

七、总结

AVL树通过严格维持每个节点的平衡因子在[-1,1]之间,保证了树的高度始终为O(log n)。其核心在于插入和删除后的回溯调整,通过四种旋转操作(LL、RR、LR、RL)来恢复平衡。虽然调整增加了常数时间开销,但换来了最坏情况下稳定的O(log n)操作时间复杂度,适用于需要频繁查找而插入删除相对较少的场景。理解并实现AVL树,是深入理解自平衡数据结构的重要一步。

手写AVL树(平衡二叉搜索树)的实现 AVL树是一种 自平衡的二叉搜索树 ,在插入和删除操作后,能通过旋转操作自动调整树的结构,使树保持平衡,从而保证查找、插入、删除操作的时间复杂度均为O(log n)。它的核心特性是: 任何节点的左右子树的高度差(平衡因子)的绝对值不超过1 。 一、核心概念与定义 平衡因子 :对于任意节点,其平衡因子 = 左子树高度 - 右子树高度。在AVL树中,平衡因子的值只能是-1、0、1。 旋转操作 :当插入或删除节点导致树不平衡时(即某个节点的平衡因子变为2或-2),需要通过旋转来恢复平衡。旋转分为四种基本类型。 二、节点结构 我们先定义树节点,它除了存储键值、左右子节点指针外,还需要存储当前节点的高度(或平衡因子)。 三、辅助函数 实现几个辅助函数,用于计算高度、更新高度、计算平衡因子。 四、旋转操作 旋转操作是AVL树实现平衡的核心。根据不平衡节点的平衡因子以及其较高子树(导致不平衡的那一侧子树)的平衡因子,分为四种情况。 1. 左旋(LL型) 当节点A的平衡因子为-2,且其右子节点B的平衡因子为-1或0时,进行左旋。 情况描述:在A的右子树的右侧插入了新节点,导致A不平衡。 操作:以A的右子节点B为支点,将A向左旋转下来,使B成为新的根节点。 2. 右旋(RR型) 当节点A的平衡因子为2,且其左子节点B的平衡因子为1或0时,进行右旋。 情况描述:在A的左子树的左侧插入了新节点,导致A不平衡。 操作:以A的左子节点B为支点,将A向右旋转下来,使B成为新的根节点。 3. 左右旋(LR型) 当节点A的平衡因子为2,且其左子节点B的平衡因子为-1时,先对B进行左旋,再对A进行右旋。 情况描述:在A的左子树的右侧插入了新节点。 操作:先左旋B,将情况转换为RR型,再右旋A。 4. 右左旋(RL型) 当节点A的平衡因子为-2,且其右子节点B的平衡因子为1时,先对B进行右旋,再对A进行左旋。 情况描述:在A的右子树的左侧插入了新节点。 操作:先右旋B,将情况转换为LL型,再左旋A。 五、插入操作 插入操作与BST插入类似,但插入后需要从插入点向上回溯,更新祖先节点的高度,并检查平衡因子,如果不平衡则进行相应的旋转。 六、删除操作 删除操作也与BST删除类似,但删除后同样需要回溯调整平衡。删除有三种情况: 要删除的节点是叶子节点:直接删除。 要删除的节点只有一个子节点:用子节点替换它。 要删除的节点有两个子节点:找到其右子树中的最小节点(或左子树中的最大节点),用这个最小节点的键值替换要删除节点的键值,然后递归删除那个最小节点。 删除后,同样需要更新高度并检查平衡因子,进行必要的旋转。 七、总结 AVL树通过 严格维持每个节点的平衡因子在[ -1,1]之间 ,保证了树的高度始终为O(log n)。其核心在于 插入和删除后的回溯调整 ,通过四种旋转操作(LL、RR、LR、RL)来恢复平衡。虽然调整增加了常数时间开销,但换来了最坏情况下稳定的O(log n)操作时间复杂度,适用于需要频繁查找而插入删除相对较少的场景。理解并实现AVL树,是深入理解自平衡数据结构的重要一步。