返回

TypeScript 数据结构与算法:红黑树

前端

红黑树:平衡二叉搜索树

红黑树是一种自平衡的二叉搜索树,它通过强制每个节点满足特定特性来保持平衡,从而实现高效的搜索、插入和删除操作。理解红黑树的原理和实现对于数据结构和算法工程师来说至关重要。

特性

红黑树的平衡特性包括:

  1. 每个节点要么是红色的,要么是黑色的。
  2. 根节点总是黑色的。
  3. 每个红色节点的两个子节点必须是黑色的。
  4. 从任一节点到其后代节点的路径上,经过的黑色节点的数量必须相等。

这些特性共同确保了红黑树的平衡,保证了任何操作的平均时间复杂度为 O(log n)。

实现

节点类

红黑树的节点由以下属性组成:

  • 键值(key)
  • 值(value)
  • 颜色(color,可以是 "red" 或 "black")
  • 左子节点(left)
  • 右子节点(right)

红黑树类

红黑树类管理树的结构,提供操作方法:

  • 插入 (insert) :将新节点插入树中,保持平衡。
  • 删除 (delete) :删除指定键值的节点,保持平衡。
  • 查找 (find) :查找指定键值的节点。

插入算法

插入操作遵循以下步骤:

  1. 将新节点插入树中,作为叶节点。
  2. 如果新节点的父节点是红色的,则进行调整,以满足红黑树的平衡特性。
  3. 调整的过程可能涉及到旋转、颜色翻转等操作。

删除算法

删除操作遵循以下步骤:

  1. 找到要删除的节点。
  2. 如果要删除的节点有两个子节点,则将其左子树的最大节点或右子树的最小节点复制到要删除的节点上。
  3. 将要删除的节点的子节点连接到其父节点上。
  4. 调整树的结构,以满足红黑树的平衡特性。

查找算法

查找操作遵循以下步骤:

  1. 从根节点开始,比较要查找的键值与当前节点的键值。
  2. 如果相等,则返回当前节点。
  3. 如果要查找的键值比当前节点的键值小,则继续搜索左子树。
  4. 如果要查找的键值比当前节点的键值大,则继续搜索右子树。

旋转操作

旋转操作用于调整树的结构,以满足红黑树的平衡特性:

  • 左旋 (leftRotate) :将当前节点的右子节点作为父节点,当前节点作为右子节点。
  • 右旋 (rightRotate) :将当前节点的左子节点作为父节点,当前节点作为左子节点。

代码示例

以下是用 TypeScript 实现红黑树的代码示例:

class Node {
  key: number;
  value: any;
  color: "red" | "black";
  left: Node | null;
  right: Node | null;

  constructor(key: number, value: any) {
    this.key = key;
    this.value = value;
    this.color = "red";
    this.left = null;
    this.right = null;
  }
}

class RedBlackTree {
  root: Node | null;

  constructor() {
    this.root = null;
  }

  // 插入操作
  insert(key: number, value: any) {
    const newNode = new Node(key, value);
    this._insert(newNode);
  }

  _insert(node: Node) {
    if (this.root === null) {
      this.root = node;
    } else {
      this._insertNode(node, this.root);
    }

    this._fixInsert(node);
  }

  _insertNode(node: Node, parent: Node) {
    if (node.key < parent.key) {
      if (parent.left === null) {
        parent.left = node;
      } else {
        this._insertNode(node, parent.left);
      }
    } else {
      if (parent.right === null) {
        parent.right = node;
      } else {
        this._insertNode(node, parent.right);
      }
    }
  }

  _fixInsert(node: Node) {
    while (node !== this.root && node.parent.color === "red") {
      if (node.parent === node.parent.parent.left) {
        const uncle = node.parent.parent.right;

        if (uncle.color === "red") {
          node.parent.color = "black";
          uncle.color = "black";
          node.parent.parent.color = "red";
          node = node.parent.parent;
        } else {
          if (node === node.parent.right) {
            node = node.parent;
            this._leftRotate(node);
          }

          node.parent.color = "black";
          node.parent.parent.color = "red";
          this._rightRotate(node.parent.parent);
        }
      } else {
        const uncle = node.parent.parent.left;

        if (uncle.color === "red") {
          node.parent.color = "black";
          uncle.color = "black";
          node.parent.parent.color = "red";
          node = node.parent.parent;
        } else {
          if (node === node.parent.left) {
            node = node.parent;
            this._rightRotate(node);
          }

          node.parent.color = "black";
          node.parent.parent.color = "red";
          this._leftRotate(node.parent.parent);
        }
      }
    }

    this.root.color = "black";
  }

  // 删除操作
  delete(key: number) {
    const node = this._findNode(key);
    if (node === null) {
      return;
    }

    this._deleteNode(node);
  }

  _deleteNode(node: Node) {
    let x: Node;
    let y: Node;

    if (node.left === null || node.right === null) {
      y = node;
    } else {
      y = this._findMaxNode(node.left);
    }

    if (y.left !== null) {
      x = y.left;
    } else {
      x = y.right;
    }

    x.parent = y.parent;

    if (y.parent === null) {
      this.root = x;
    } else if (y === y.parent.left) {
      y.parent.left = x;
    } else {
      y.parent.right = x;
    }

    if (y !== node) {
      node.key = y.key;
      node.value = y.value;
    }

    if (y.color === "black") {
      this._fixDelete(x);
    }
  }

  _fixDelete(node: Node) {
    while (node !== this.root && node.color === "black") {
      if (node === node.parent.left) {
        const brother = node.parent.right;

        if (brother.color === "red") {
          brother.color = "black";
          node.parent.color = "red";
          this._leftRotate(node.parent);
          brother = node.parent.right;
        }

        if (brother.left.color === "black" && brother.right.color === "black") {
          brother.color = "red";
          node = node.parent;
        } else {
          if (brother.right.color === "black") {
            brother.left.color = "black";
            brother.color = "red";
            this._rightRotate(brother);
            brother = node.parent.right;
          }

          brother.color = node.parent.color;
          node.parent.color = "black";
          brother.right.color = "black";
          this._leftRotate(node.parent);
          node = this.root;
        }
      } else {
        const brother = node.parent.left;

        if (brother.color === "red") {
          brother.color = "black";
          node.parent.color = "red";
          this._rightRotate(node.parent);
          brother = node.parent.left;
        }

        if (brother.left.color === "black" && brother.right.color === "black") {
          brother.color = "red";
          node = node