二叉平衡树

62 阅读3分钟

二叉平衡树(AVL 树)

二叉平衡树(AVL 树)是一种自平衡的二叉搜索树,它通过保持树的平衡性来提供快速的搜索、插入和删除操作。它在设计上确保了左子树和右子树的高度差不超过 1,从而保持了树的平衡。

特点

  • 每个节点都有一个平衡因子(Balance Factor),它表示左子树高度和右子树高度之差。平衡因子可以是 -1、0 或 1。
  • 每个节点的左子树和右子树也是 AVL 树。
  • AVL 树中的每个节点都存储一个关键字,关键字按照一定的顺序排列。

平衡操作

当插入或删除节点后,可能会破坏 AVL 树的平衡性。为了恢复平衡,AVL 树使用四种基本的平衡操作:

  1. 左旋转(Left Rotation):在一个节点的右子树上进行旋转操作,使得右子树的根节点成为新的根节点,原来的根节点成为新根节点的左子树。
  2. 右旋转(Right Rotation):在一个节点的左子树上进行旋转操作,使得左子树的根节点成为新的根节点,原来的根节点成为新根节点的右子树。
  3. 左-右双旋转(Left-Right Double Rotation):先对当前节点的左子树进行右旋转,然后再对当前节点进行左旋转。
  4. 右-左双旋转(Right-Left Double Rotation):先对当前节点的右子树进行左旋转,然后再对当前节点进行右旋转。

这些平衡操作通过调整树的结构来保持平衡,并且保持所有节点的平衡因子符合 AVL 树的定义。

时间复杂度

  • 搜索操作的平均时间复杂度为 O(log n),其中 n 是树中节点的数量。
  • 插入和删除操作的平均时间复杂度也为 O(log n),因为在插入或删除后需要执行平衡操作来恢复树的平衡。

举个🌰

将个长度为1w的数组转换为二叉平衡树的前提是必须先将数组排序

class TreeNode {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

function buildBalancedBST(nums) {
  if (nums.length === 0) {
    return null;
  }

  const mid = Math.floor(nums.length / 2); // 找到中间位置
  const root = new TreeNode(nums[mid]); // 中间元素作为根节点

  // 递归构建左子树和右子树
  root.left = buildBalancedBST(nums.slice(0, mid));
  root.right = buildBalancedBST(nums.slice(mid + 1));

  return root;
}

// 示例用法
const nums = Array.from({ length: 10000 }, (_, index) => Math.Random() * 10000); // 构建 1 10000 的数组
nums.sort((a, b) => a -b);
const balancedBST = buildBalancedBST(nums);
console.log(balancedBST);

优点和应用

  • AVL 树提供了快速的搜索、插入和删除操作,特别适用于需要频繁执行这些操作的场景。
  • AVL 树的平衡性保证了搜索和插入操作的时间复杂度较低,使得它在大量动态数据的情况下仍然能够保持高效。
  • AVL 树广泛应用于数据库

获取树的深度

function getDeep(root) {
  if (root === null) {
    return 0;
  }
  const left = getDeep(root.left);
  const right = getDeep(root.right);
  return Math.max(left, right) + 1;
}

判断是否为二叉平衡树

只要每个节点的的左子树和右子树的高度差小于1那么他就是二叉平衡树

function isBalance(root) {
  if(root === null) {
    return false;
  }
  const leftDeep = getDeep(root.left);
  const rightDeep  = getDeep(root.right);
  if(Math.abs(leftDeep - rightDeep) > 1) {
    return false;
  }
  return isBalance(root.left) && isBalance(root.right);
}

AVL的右旋和左旋

这个图清晰明了

根节点S的左子树高度大于右子树,所以要向右平衡。

  1. 将S的左节点E作为新的根节点
  2. 将E的右节点作为S的左节点

right.gif

根节点E的右子树高度大于左子树,所以要向左平衡。

  1. 将E的右节点S作为新的根节点
  2. 将S的左节点作为E的右节点 24fe483b61884549ab38aecbcac0428f~tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.gif

二叉树经过左旋后还可以通过右旋将其还原回来。同理右旋也可以通过左旋还原。

代码实现左旋和右旋

function rotateLeft(root) {
  const tem = root.right;
  tem.left = root;
  root.right = tem.left;
  return tem;
}
function rotateRight(root) {
  const temp = root.left;
  temp.right = root;
  root.left = temp.right;
  return temp;
}

代码实现AVL的平衡

function change(root){
  if(isBalance(root)) {
    return root;
  }

  if(root.right !== null) {
    root.right = change(root.right)
  }
  if(root.left !== null) {
    root.left = change(root.left)
  }
  const leftDeep = getDeep(root.left)
  const rightDeep = getDeep(root.right)

  if(leftDeep > rightDeep) {
    // 右旋
    return rotateRight(root)
  } else {
    // 左旋
    return rotateLeft(root)
  }
}