学习:平衡二叉树的旋转机制

        平衡二叉树是一种特殊的二叉搜索树(BST),通过保持树的高度平衡,确保在最坏情况下操作的时间复杂度维持在 (O(log n))。本文将深入探讨平衡二叉树的基本概念、节点添加和查找、旋转机制及其触发时机,并详细解释四种基本旋转操作:左左(LL)、左右(LR)、右右(RR)和右左(RL)。

前言

        平衡二叉树(Balanced Binary Tree)是一种二叉树,其特点是在插入或删除节点时,通过一定的机制保持树的平衡。常见的平衡二叉树包括 AVL 树红黑树。本文主要以 AVL 树为例,讲解其工作原理和实现细节。

添加节点

        在 AVL 树中,添加节点的过程与普通的二叉搜索树类似,但在每次插入后,需要检查树的平衡因子并进行必要的旋转操作以恢复平衡。以下是添加节点的具体步骤:

1. 普通的 BST 插入:
    - 比较要插入的值与当前节点的值。
    - 如果小于当前节点的值,递归地插入到左子树。
    - 如果大于当前节点的值,递归地插入到右子树。
    - 如果等于当前节点的值,则忽略(不允许重复)。

2. 更新高度:
    - 插入节点后,更新沿路径的每个节点的高度。

3. 检查平衡因子:
    - 计算每个节点的平衡因子(左子树高度减去右子树高度)。

4. 执行旋转操作:
    - 根据平衡因子的值,确定是否需要旋转及旋转类型。

插入节点示例代码:

public class AVLTree {
    private class TreeNode {
        int key, height;
        TreeNode left, right;

        TreeNode(int d) {
            key = d;
            height = 1;
        }
    }

    private TreeNode root;

    public void insert(int key) {
        root = insertRec(root, key);
    }

    private TreeNode insertRec(TreeNode node, int key) {
        // 如果当前节点为空,则插入新节点
        if (node == null) {
            return new TreeNode(key);
        }

        // 根据键值大小选择插入到左子树或右子树
        if (key < node.key) {
            node.left = insertRec(node.left, key);
        } else if (key > node.key) {
            node.right = insertRec(node.right, key);
        } else {
            return node; // 不允许插入重复键值
        }

        // 更新节点的高度
        node.height = 1 + Math.max(height(node.left), height(node.right));

        // 计算平衡因子
        int balance = getBalance(node);

        // 根据平衡因子进行相应的旋转操作

        // 左左情况
        if (balance > 1 && key < node.left.key) {
            return rightRotate(node);
        }

        // 右右情况
        if (balance < -1 && key > node.right.key) {
            return leftRotate(node);
        }

        // 左右情况
        if (balance > 1 && key > node.left.key) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }

        // 右左情况
        if (balance < -1 && key < node.right.key) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }

        return node;
    }

    // 获取节点的高度
    private int height(TreeNode node) {
        return node == null ? 0 : node.height;
    }

    // 计算节点的平衡因子
    private int getBalance(TreeNode node) {
        return node == null ? 0 : height(node.left) - height(node.right);
    }

    // 右旋操作
    private TreeNode rightRotate(TreeNode y) {
        TreeNode x = y.left;
        TreeNode T2 = x.right;

        // 执行旋转操作
        x.right = y;
        y.left = T2;

        // 更新旋转节点的高度
        y.height = Math.max(height(y.left), height(y.right)) + 1;
        x.height = Math.max(height(x.left), height(x.right)) + 1;

        // 返回新的根节点
        return x;
    }

    // 左旋操作
    private TreeNode leftRotate(TreeNode x) {
        TreeNode y = x.right;
        TreeNode T2 = y.left;

        // 执行旋转操作
        y.left = x;
        x.right = T2;

        // 更新旋转节点的高度
        x.height = Math.max(height(x.left), height(x.right)) + 1;
        y.height = Math.max(height(y.left), height(y.right)) + 1;

        // 返回新的根节点
        return y;
    }
}

在平衡二叉树中,如何查找单个节点?

        查找节点的过程与普通的二叉搜索树相同。通过比较要查找的值与当前节点的值,递归地在左子树或右子树中查找。

public boolean search(int key) {
    return searchRec(root, key);
}

private boolean searchRec(TreeNode node, int key) {
    if (node == null) {
        return false;
    }

    if (key < node.key) {
        return searchRec(node.left, key);
    } else if (key > node.key) {
        return searchRec(node.right, key);
    } else {
        return true;
    }
}

为什么要旋转?

        在 AVL 树中,每个节点的平衡因子是其左子树高度减去右子树高度的值。AVL 树通过旋转操作保持平衡因子在 -1、0 和 1 之间。当插入或删除节点后,某个节点的平衡因子的绝对值超过 1 时,树就需要进行旋转操作来恢复平衡。

旋转的触发时机

旋转操作在以下情况下触发:

1. 插入节点后:如果插入节点导致某个节点的平衡因子变为 2 或 -2。
2. 删除节点后:如果删除节点导致某个节点的平衡因子变为 2 或 -2。

左左(LL)旋转

        左左情况发生在一个节点的左子树过高(平衡因子为 2),且左子树的左子树导致不平衡。这种情况需要进行一次右旋操作。

     z
    / \
   y   T4
  / \
 x  T3
/ \
T1 T2

右旋后:

      y
    /   \
   x     z
  / \   / \
 T1 T2 T3 T4

右旋操作步骤

1. 识别旋转节点:找到需要进行右旋的节点 z。
2. 左子树提取:提取 z 的左子树 y,并将 y 的右子树 T2 暂存。
3. 旋转操作:
   - 将 y 的右子树指向 z。
   - 将 z 的左子树指向 T2。
4. 更新高度:更新 z 和 y 的高度。
5. 返回新的根节点:返回旋转后的新根节点 y。

右旋代码实现:

private TreeNode rightRotate(TreeNode y) {
    TreeNode x = y.left;
    TreeNode T2 = x.right;

    // 执行旋转操作
    x.right = y;
    y.left = T2;

    // 更新旋转节点的高度
    y.height = Math.max(height(y.left), height(y.right)) + 1;
    x.height = Math.max(height(x.left), height(x.right)) + 1;

    // 返回新的根节点
    return x;
}

左右(LR)旋转

        左右情况发生在一个节点的左子树过高(平衡因子为 2),且左子树的右子树导致不平衡。这种情况需要先对左子树进行左旋,然后对根节点进行右旋。

     z
    / \
   y   T4
  / \
 T1  x
    / \
   T2 T3

左旋后:

      z
     / \
    x   T4
   / \
  y  T3
 / \
T1 T2

右旋后:

      x
    /   \
   y     z
  / \   / \
 T1 T2 T3 T4

左右旋操作步骤

1. 先对左子树进行左旋:
   - 提取 y 的右子树 x,并将 x 的左子树 T2 暂存。
   - 将 y 的右子树指向 T2,将 x 的左子树指向 y。
   - 更新 y 和 x 的高度。
2. 再对根节点进行右旋:
   - 提取 z 的左子树 x,并将 x 的右子树 T3 暂存。
   - 将 z 的左子树指向 T3,将 x 的右子树指向 z。
   - 更新 z 和 x 的高度。
3. 返回新的根节点:返回旋转后的新根节点 x。

左右旋代码实现:

private TreeNode leftRightRotate(TreeNode z) {
    // 先对左子树进行左旋
    z.left = leftRotate(z.left);
    // 然后对根节点进行右旋
    return rightRotate(z);
}

右右(RR)旋转

        右右情况发生在一个节点的右子树过高(平衡因子为 -2),且右子树的右子树导致不平衡。这种情况需要进行一次左旋操作。

     z
    / \
   T1  y
      / \
     T2  x
        / \
       T3 T4

左旋后:
 

      y
    /   \
   z     x
  / \   / \
 T1 T2 T3 T4

左旋操作步骤

1. 识别旋转节点:找到需要进行左旋的节点 z。
2. 右子树提取:提取 z 的右子树 y,并将 y 的左子树 T2 暂存。
3. 旋转操作:
   - 将 y 的左子树指向 z。
   - 将 z 的右子树指向 T2。
4. 更新高度:更新 z 和 y 的高度。
5. 返回新的根节点:返回旋转后的新根节点 y。

左旋代码实现:

private TreeNode leftRotate(TreeNode x) {
    TreeNode y = x.right;
    TreeNode T2 = y.left;

    // 执行旋转操作
    y.left = x;
    x.right = T2;

    // 更新旋转节点的高度
    x.height = Math.max(height(x.left), height(x.right)) + 1;
    y.height = Math.max(height(y.left), height(y.right)) + 1;

    // 返回新的根节点
    return y;
}

右左(RL)旋转

右左情况发生在一个节点的右子树过高(平衡因子为 -2),且右子树的左子树导致不平衡。这种情况需要先对右子树进行右旋,然后对根节点进行左旋。

     z
    / \
   T1  y
      / \
     x  T4
    / \
   T2 T3

右旋后:

     z
    / \
   T1  x
      / \
     T2  y
         / \
        T3 T4

左旋后:

      x
    /   \
   z     y
  / \   / \
 T1 T2 T3 T4

右左旋操作步骤

1. 先对右子树进行右旋:
   - 提取 y 的左子树 x,并将 x 的右子树 T3 暂存。
   - 将 y 的左子树指向 T3,将 x 的右子树指向 y。
   - 更新 y 和 x 的高度。
2. 再对根节点进行左旋:
   - 提取 z 的右子树 x,并将 x 的左子树 T2 暂存。
   - 将 z 的右子树指向 T2,将 x 的左子树指向 z。
   - 更新 z 和 x 的高度。
3. 返回新的根节点:返回旋转后的新根节点 x。

右左旋代码实现:

private TreeNode rightLeftRotate(TreeNode z) {
    // 先对右子树进行右旋
    z.right = rightRotate(z.right);
    // 然后对根节点进行左旋
    return leftRotate(z);
}

总结

        平衡二叉树通过旋转操作保持树的平衡,从而确保操作的时间复杂度维持在 \(O(\log n)\)。四种基本的旋转操作(左左、左右、右右和右左)是维护树平衡的核心手段。理解这些旋转操作及其应用,可以帮助我们设计和实现高效的数据结构,确保在各种应用场景中提供稳定的性能。

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值