红黑树是什么?DSA里怎么实现和用?

文章导读
上一个 测验 下一个 红黑树(Red-Black Trees)是另一种平衡二叉搜索树,具有两种颜色的节点:红色和黑色。它是一种自平衡二叉搜索树,利用这些颜色在插入和删除操作期间维护平衡因子。因此,在红黑树操作期间,内存使用 1 bit 的存储空间来容纳每个节点颜色信息。
📋 目录
  1. 红黑树的基本操作
  2. 插入操作
  3. 删除操作
  4. 搜索操作
  5. 完整实现
A A

红黑树

目录
  • 红黑树的基本操作
  • 插入操作
  • 删除操作
  • 搜索操作
  • 完整实现


上一个
测验
下一个

红黑树(Red-Black Trees)是另一种平衡二叉搜索树,具有两种颜色的节点:红色和黑色。它是一种自平衡二叉搜索树,利用这些颜色在插入和删除操作期间维护平衡因子。因此,在红黑树操作期间,内存使用 1 bit 的存储空间来容纳每个节点颜色信息。

在红黑树(也称为 RB trees)中,为节点分配颜色时需要遵循不同的条件。

  • 根节点始终为黑色。

  • 不能有两个相邻的红色节点。

  • 树中的每条路径(从根节点到叶节点)必须包含相同数量的黑色节点。

尽管 AVL 树比 RB 树更加平衡,AVL 树的平衡算法比 RB 树的更严格,但通过 RB 树可以更高效地进行多次快速的插入和删除操作。

RB trees

图:红黑树

红黑树的基本操作

红黑树的操作包括通常在二叉搜索树上执行的所有基本操作。RB 树的一些基本操作包括:

  • 插入

  • 删除

  • 搜索

插入操作

红黑树的插入操作遵循二叉搜索树的相同插入算法。元素按照二叉搜索属性插入,此外,节点被着色为红色和黑色,以根据红黑树属性平衡树。

按照以下步骤插入元素到红黑树中,同时维护二叉搜索树和红黑树属性。

情况 1 − 检查树是否为空;如果为空,则将当前节点设为根节点并将其着色为黑色。

情况 2 − 但如果树不为空,我们创建一个新节点并将其着色为红色。在此,我们面临两种不同的情况 −

  • 如果新节点的父节点是黑色节点,则退出操作,树保持不变。

  • 如果新节点的父节点是红色,且父节点的兄弟节点颜色为黑色或不存在,则应用合适的旋转并相应重新着色。

  • 如果新节点的父节点是红色,且父节点的兄弟节点颜色为红色,则将父节点、兄弟节点和祖父节点重新着色为黑色。只有当祖父节点不是根节点时才重新着色祖父节点;如果它是根节点,则只重新着色父节点和兄弟节点。

示例

让我们为前 7 个整数构建一棵 RB 树,以详细理解插入操作 −

树被检查为空,因此第一个添加的节点是根节点并被着色为黑色。

first node

现在,树不为空,因此我们创建一个新节点并添加下一个整数,颜色为红色,

new node

节点没有违反二叉搜索树和 RB 树属性,因此我们继续添加另一个节点。

树不为空;我们为下一个整数创建一个新的红色节点并添加。但新节点的父节点不是黑色节点,

third node

此时树违反了二叉搜索树和 RB 树属性;由于父节点的兄弟节点为 NULL,我们应用合适的旋转并重新着色节点。

suitable rotation

现在 RB 树属性已恢复,我们向树中添加另一个节点 −

RB Tree property

树再次违反了 RB 树平衡属性,因此我们检查父节点的兄弟节点颜色,此例中为红色,因此我们只需重新着色父节点和兄弟节点。

RB Tree balance property

接下来插入元素 5,这使得树再次违反 RB 树平衡属性。

insert element 5

由于兄弟节点为 NULL,我们应用合适的旋转并重新着色。

sibling is NULL

现在,我们插入元素 6,但 RB 树属性被违反,需要应用插入情况之一 −

insert element 6

父节点的兄弟节点为红色,因此我们重新着色父节点、父节点的兄弟节点和祖父节点,因为祖父节点不是根节点。

recolor parent

现在,我们添加最后一个元素 7,但这个新节点的父节点是红色。

add last element

由于父节点的兄弟节点为 NULL,我们应用合适的旋转(RR 旋转)

RB Tree achieved

最终的 RB 树已构建完成。

删除操作

红黑树上的删除操作必须以这样的方式执行,即必须恢复二叉搜索树和红黑树的所有性质。按照以下步骤在红黑树上执行删除操作 −

首先,我们基于二叉搜索树的性质进行删除。

情况 1 − 如果要删除的节点或该节点的父节点是红色的,直接删除它。

情况 2 − 如果节点是双黑色的,直接移除双黑色(双黑色发生在要删除的节点是黑色叶子节点时,因为它会累加视为黑色节点的 NULL 节点)

情况 3 − 如果双黑色的兄弟节点也是黑色节点且其子节点也是黑色的,按照以下步骤操作 −

  • 移除双黑色

  • 将父节点重新着色为黑色(如果父节点是红色节点,则变为黑色;如果父节点已经是黑色节点,则变为双黑色)

  • 将父节点的兄弟节点重新着色为红色

  • 如果双黑色节点仍然存在,则应用其他情况。

情况 4 − 如果双黑色的兄弟节点是红色的,我们执行以下步骤 −

  • 交换父节点和父节点兄弟节点的颜色。

  • 向双黑色的方向旋转父节点

  • 重新应用其他适用的情况。

情况 5 − 如果双黑色的兄弟节点是黑色节点,但兄弟节点最靠近双黑色的子节点是红色的,按照以下步骤操作 −

  • 交换双黑色兄弟节点和该兄弟节点相关子节点的颜色

  • 向双黑色的反方向旋转兄弟节点(即如果双黑色是右子节点,则应用左旋转,反之亦然)

  • 应用情况 6。

情况 6 − 如果双黑色的兄弟节点是黑色节点,但兄弟节点远离双黑色的子节点是红色的,按照以下步骤操作 −

  • 交换双黑色父节点和兄弟节点的颜色

  • 向双黑色的方向旋转父节点(即如果双黑色是右子节点,则应用右旋转,反之亦然)

  • 移除双黑色

  • 将红色子节点颜色改为黑色。

示例

考虑上述构建的相同的红黑树,让我们从树中删除几个元素。

RB Tree achieved

从树中删除元素 4、5、3。

要删除元素 4,我们首先执行二叉搜索删除。

delete element 4

执行二叉搜索删除后,红黑树性质未被破坏,因此树保持原样。

然后,我们使用二叉搜索删除删除元素 5

delete element 5

但是执行二叉搜索删除后违反了红黑树性质,即树中所有路径不具有相同数量的黑色节点;因此我们交换颜色以平衡树。

RB property violated

然后,我们从获得树中删除节点 3 −

应用二叉搜索删除,我们正常删除叶子节点 3。而且由于 3 是黑色节点,我们得到一个双黑色节点。

delete node 3

我们应用情况 3 删除,因为双黑色的兄弟节点是黑色且其子节点也是黑色。这里,我们移除双黑色,重新着色双黑色的父节点和兄弟节点。

RB Tree property maintained

所有所需的节点已被删除,并且红黑树性质得到维护。

搜索操作

红黑树中的搜索操作遵循与二叉搜索树相同的算法。遍历树并将每个节点与要搜索的关键元素进行比较;如果找到则返回成功搜索。否则,返回不成功搜索。

完整实现

以下是各种编程语言中 Red Black Tree 的完整实现 −

C++ Java Python
// C++ Red Black Tree 算法程序
#include <iostream>
using namespace std;
struct Node {
  int data;
  Node *parent;
  Node *left;
  Node *right;
  int color;
};
typedef Node *NodePtr;
class RedBlackTree {
   private:
  NodePtr root;
  NodePtr TNULL;
  void initializeNULLNode(NodePtr node, NodePtr parent) {
    node->data = 0;
    node->parent = parent;
    node->left = nullptr;
    node->right = nullptr;
    node->color = 0;
  }
  // 前序遍历
  void preOrderHelper(NodePtr node) {
    if (node != TNULL) {
      cout << node->data << " ";
      preOrderHelper(node->left);
      preOrderHelper(node->right);
    }
  }
  // 中序遍历
  void inOrderHelper(NodePtr node) {
    if (node != TNULL) {
      inOrderHelper(node->left);
      cout << node->data << " ";
      inOrderHelper(node->right);
    }
  }
  // 后序遍历
  void postOrderHelper(NodePtr node) {
    if (node != TNULL) {
      postOrderHelper(node->left);
      postOrderHelper(node->right);
      cout << node->data << " ";
    }
  }
  NodePtr searchTreeHelper(NodePtr node, int key) {
    if (node == TNULL || key == node->data) {
      return node;
    }
    if (key < node->data) {
      return searchTreeHelper(node->left, key);
    }
    return searchTreeHelper(node->right, key);
  }
  // 删除后平衡树
  void deleteFix(NodePtr x) {
    NodePtr s;
    while (x != root && x->color == 0) {
      if (x == x->parent->left) {
        s = x->parent->right;
        if (s->color == 1) {
          s->color = 0;
          x->parent->color = 1;
          leftRotate(x->parent);
          s = x->parent->right;
        }
        if (s->left->color == 0 && s->right->color == 0) {
          s->color = 1;
          x = x->parent;
        } else {
          if (s->right->color == 0) {
            s->left->color = 0;
            s->color = 1;
            rightRotate(s);
            s = x->parent->right;
          }
          s->color = x->parent->color;
          x->parent->color = 0;
          s->right->color = 0;
          leftRotate(x->parent);
          x = root;
        }
      } else {
        s = x->parent->left;
        if (s->color == 1) {
          s->color = 0;
          x->parent->color = 1;
          rightRotate(x->parent);
          s = x->parent->left;
        }
        if (s->right->color == 0 && s->right->color == 0) {
          s->color = 1;
          x = x->parent;
        } else {
          if (s->left->color == 0) {
            s->right->color = 0;
            s->color = 1;
            leftRotate(s);
            s = x->parent->left;
          }
          s->color = x->parent->color;
          x->parent->color = 0;
          s->left->color = 0;
          rightRotate(x->parent);
          x = root;
        }
      }
    }
    x->color = 0;
  }
  void rbTransplant(NodePtr u, NodePtr v) {
    if (u->parent == nullptr) {
      root = v;
    } else if (u == u->parent->left) {
      u->parent->left = v;
    } else {
      u->parent->right = v;
    }
    v->parent = u->parent;
  }
  void deleteNodeHelper(NodePtr node, int key) {
    NodePtr z = TNULL;
    NodePtr x, y;
    while (node != TNULL) {
      if (node->data