返回

一文吃透 AVL/红黑树,并附上可视化代码

前端

前言
二叉树是一种非常重要的数据结构,它在计算机科学中有着广泛的应用。然而,当二叉树的数据量不断增加时,很容易出现不平衡的情况,即树的高度过高。这会导致树的搜索、插入和删除操作变得非常低效。为了解决这个问题,人们发明了AVL树和红黑树这两种平衡树。

AVL树

AVL树是一种高度平衡的二叉搜索树,它通过维护节点的平衡因子来确保树的高度不会过高。平衡因子是指一个节点的左子树的高度减去其右子树的高度。AVL树的平衡因子只能取-1、0、1三个值。如果一个节点的平衡因子不是0,则说明该节点不平衡。

当AVL树需要插入或删除节点时,它会进行旋转操作以确保树的平衡性。旋转操作是指将一个节点与其子节点进行交换,以使树的高度不会过高。

红黑树

红黑树也是一种高度平衡的二叉搜索树,但它使用一种不同的方法来维护树的平衡性。红黑树规定节点只能是红色或黑色,并且根节点必须是黑色。红黑树的平衡规则如下:

  • 每个节点的子节点要么都是红色,要么都是黑色。
  • 根节点必须是黑色。
  • 从一个红色节点到其任何后代的简单路径上,不能存在两个连续的红色节点。
  • 从任意一个节点到其后代叶节点的简单路径上的黑节点数量必须相同。

当红黑树需要插入或删除节点时,它会进行旋转和颜色翻转操作以确保树的平衡性和满足红黑树的规则。

AVL树和红黑树的比较

AVL树和红黑树都是高度平衡的二叉搜索树,它们都有着良好的搜索、插入和删除性能。然而,它们也有一些区别。

  • AVL树的平衡因子必须为-1、0或1,而红黑树的平衡规则则更复杂一些。
  • AVL树的旋转操作更简单,而红黑树的旋转操作更复杂。
  • AVL树的插入和删除操作的平均时间复杂度为O(log n),而红黑树的插入和删除操作的平均时间复杂度为O(log n)。

AVL树和红黑树的可视化

为了更好地理解AVL树和红黑树的原理,我们可以使用可视化工具来实现它们。在本文中,我将使用JavaScript和Canvas来实现AVL树和红黑树的可视化。

可视化代码如下:

// AVL树的可视化代码
class AVLTree {
  constructor() {
    this.root = null;
  }

  insert(key) {
    this.root = this._insert(key, this.root);
  }

  _insert(key, node) {
    if (node === null) {
      return new Node(key);
    } else if (key < node.key) {
      node.left = this._insert(key, node.left);
    } else if (key > node.key) {
      node.right = this._insert(key, node.right);
    } else {
      node.count++;
    }

    node.height = Math.max(this._getHeight(node.left), this._getHeight(node.right)) + 1;
    node.balanceFactor = this._getBalanceFactor(node);

    if (node.balanceFactor > 1) {
      if (node.left.balanceFactor < 0) {
        node.left = this._leftRotate(node.left);
      }
      return this._rightRotate(node);
    } else if (node.balanceFactor < -1) {
      if (node.right.balanceFactor > 0) {
        node.right = this._rightRotate(node.right);
      }
      return this._leftRotate(node);
    }

    return node;
  }

  _getHeight(node) {
    if (node === null) {
      return -1;
    } else {
      return node.height;
    }
  }

  _getBalanceFactor(node) {
    if (node === null) {
      return 0;
    } else {
      return this._getHeight(node.left) - this._getHeight(node.right);
    }
  }

  _leftRotate(node) {
    const rightChild = node.right;
    node.right = rightChild.left;
    rightChild.left = node;

    node.height = Math.max(this._getHeight(node.left), this._getHeight(node.right)) + 1;
    rightChild.height = Math.max(this._getHeight(rightChild.left), this._getHeight(rightChild.right)) + 1;

    node.balanceFactor = this._getBalanceFactor(node);
    rightChild.balanceFactor = this._getBalanceFactor(rightChild);

    return rightChild;
  }

  _rightRotate(node) {
    const leftChild = node.left;
    node.left = leftChild.right;
    leftChild.right = node;

    node.height = Math.max(this._getHeight(node.left), this._getHeight(node.right)) + 1;
    leftChild.height = Math.max(this._getHeight(leftChild.left), this._getHeight(leftChild.right)) + 1;

    node.balanceFactor = this._getBalanceFactor(node);
    leftChild.balanceFactor = this._getBalanceFactor(leftChild);

    return leftChild;
  }

  search(key) {
    return this._search(key, this.root);
  }

  _search(key, node) {
    if (node === null) {
      return null;
    } else if (key < node.key) {
      return this._search(key, node.left);
    } else if (key > node.key) {
      return this._search(key, node.right);
    } else {
      return node;
    }
  }

  min() {
    return this._min(this.root);
  }

  _min(node) {
    if (node === null) {
      return null;
    } else if (node.left === null) {
      return node;
    } else {
      return this._min(node.left);
    }
  }

  max() {
    return this._max(this.root);
  }

  _max(node) {
    if (node === null) {
      return null;
    } else if (node.right === null) {
      return node;
    } else {
      return this._max(node.right);
    }
  }

  delete(key) {
    this.root = this._delete(key, this.root);
  }

  _delete(key, node) {
    if (node === null) {
      return null;
    } else if (key < node.key) {
      node.left = this._delete(key, node.left);
    } else if (key > node.key) {
      node.right = this._delete(key, node.right);
    } else {
      if (node.count > 1) {
        node.count--;
      } else if (node.left === null) {
        node = node.right;
      } else if (node.right === null) {
        node = node.left;
      } else {
        const successor = this._min(node.right);
        node.key = successor.key;
        node.count = successor.count;
        node.right = this._delete(successor.key, node.right);
      }
    }

    if (node === null) {
      return null;
    }

    node.height = Math.max(this._getHeight(node.left), this._getHeight(node.right)) + 1;
    node.balanceFactor = this._getBalanceFactor(node);

    if (node.balanceFactor > 1) {
      if (node.left.balanceFactor < 0) {
        node.left = this._leftRotate(node.left);
      }
      return this._rightRotate(node);
    } else if (node.balanceFactor < -1) {
      if (node.right.balanceFactor > 0) {
        node.right = this._rightRotate(node.right);
      }
      return this._leftRotate(node