学习平衡二叉树(AVL)看这一篇就够了

685 阅读5分钟

平衡二叉树

平衡二叉树是一种特殊的二叉搜索树,它要求任何一个节点的左右子树的高度差不超过1,这样可以保证树的高度最小,从而提高搜索效率。平衡二叉树的英文缩写是AVL,是由它的发明者Adelson-Velsky和Landis的姓氏首字母组成的。

四种基本类型

平衡二叉树的主要操作有插入和删除,这两种操作可能会破坏树的平衡性,因此需要进行调整。调整的方法是通过旋转操作,将失衡的子树恢复平衡。旋转操作有四种基本类型,分别是LL型,RR型,LR型和RL型。

LL型是指在某个节点的左子树的左子树上插入或删除导致失衡,此时需要对该节点进行一次右旋操作,即将该节点的左子节点作为新的根节点,将该节点作为新根节点的右子节点,将原左子节点的右子树作为该节点的左子树。

RR型是指在某个节点的右子树的右子树上插入或删除导致失衡,此时需要对该节点进行一次左旋操作,即将该节点的右子节点作为新的根节点,将该节点作为新根节点的左子节点,将原右子节点的左子树作为该节点的右子树。

LR型是指在某个节点的左子树的右子树上插入或删除导致失衡,此时需要对该节点进行两次旋转操作,先对其左子节点进行一次左旋操作,再对该节点进行一次右旋操作。

RL型是指在某个节点的右子树的左子树上插入或删除导致失衡,此时需要对该节点进行两次旋转操作,先对其右子节点进行一次右旋操作,再对该节点进行一次左旋操作。

优缺点

平衡二叉树的优点是可以保证在最坏情况下仍然具有较高的搜索效率,时间复杂度为O(logn)。它也可以用于实现其他数据结构,如优先队列、集合、映射等。

平衡二叉树的缺点是插入和删除操作比较复杂,需要维护每个节点的高度和平衡因子,并且可能需要多次旋转来恢复平衡。它也不适合频繁修改数据的场景,因为每次修改都可能引起调整。

代码实现一个平衡二叉树

// 定义一个二叉树节点类
class TreeNode {
  int val; // 节点的值
  TreeNode left; // 左子节点
  TreeNode right; // 右子节点
  int height; // 节点的高度

  // 构造方法
  public TreeNode(int val) {
    this.val = val;
    this.left = null;
    this.right = null;
    this.height = 1;
  }
}

// 定义一个平衡二叉树类
class AVLTree {
  TreeNode root; // 根节点

  // 构造方法
  public AVLTree() {
    this.root = null;
  }

  // 获取节点的高度
  public int getHeight(TreeNode node) {
    if (node == null) {
      return 0;
    }
    return node.height;
  }

  // 获取节点的平衡因子
  public int getBalanceFactor(TreeNode node) {
    if (node == null) {
      return 0;
    }
    return getHeight(node.left) - getHeight(node.right);
  }

  // 右旋操作
  public TreeNode rightRotate(TreeNode node) {
    TreeNode left = node.left;
    TreeNode leftRight = left.right;

    // 右旋
    left.right = node;
    node.left = leftRight;

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

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

  // 左旋操作
  public TreeNode leftRotate(TreeNode node) {
    TreeNode right = node.right;
    TreeNode rightLeft = right.left;

    // 左旋
    right.left = node;
    node.right = rightLeft;

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

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

  // 插入操作
  public TreeNode insert(TreeNode node, int val) {
    if (node == null) {
      return new TreeNode(val);
    }

    if (val < node.val) { // 插入左子树
      node.left = insert(node.left, val);
    } else if (val > node.val) { // 插入右子树
      node.right = insert(node.right, val);
    } else { // 相等则不插入
      return node;
    }

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

    // 计算平衡因子
    int balanceFactor = getBalanceFactor(node);

    // 平衡调整
    if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) { // LL型,右旋
      return rightRotate(node);
    }
    
    if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) { // RR型,左旋
      return leftRotate(node);
    }

    if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) { // LR型,先左旋后右旋
      node.left = leftRotate(node.left);
      return rightRotate(node);
    }

    if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) { // RL型,先右旋后左旋
      node.right = rightRotate(node.right);
      return leftRotate(node);
    }

    return node;
  }

}

逻辑分析

  1. 首先,定义了一个二叉树节点类,包含了节点的值,左右子节点,和节点的高度。
  2. 然后,定义了一个平衡二叉树类,包含了根节点,和一些方法。

这些方法包括:

(1)获取节点的高度,如果节点为空,返回0,否则返回节点的高度属性。

(2)获取节点的平衡因子,如果节点为空,返回0,否则返回左子树的高度减去右子树的高度。

(3)右旋操作,用于调整左子树过高的情况。将当前节点的左子节点作为新的根节点,将左子节点的右子树作为当前节点的左子树,将当前节点作为新根节点的右子树。然后更新两个节点的高度,并返回新的根节点。

(4)左旋操作,用于调整右子树过高的情况。将当前节点的右子节点作为新的根节点,将右子节点的左子树作为当前节点的右子树,将当前节点作为新根节点的左子树。然后更新两个节点的高度,并返回新的根节点。

(5)插入操作,用于向平衡二叉树中添加一个新的值。如果当前节点为空,创建一个新的节点并返回。如果值小于当前节点的值,递归地向左子树插入。如果值大于当前节点的值,递归地向右子树插入。如果值等于当前节点的值,不做任何操作并返回。然后更新当前节点的高度,并计算其平衡因子。如果平衡因子大于1且左子树也是左倾斜的,进行右旋操作。如果平衡因子小于-1且右子树也是右倾斜的,进行左旋操作。如果平衡因子大于1且左子树是右倾斜的,先对左子树进行左旋操作,再对当前节点进行右旋操作。如果平衡因子小于-1且右子树是左倾斜的,先对右子树进行右旋操作,再对当前节点进行左旋操作。最后返回当前节点。