红黑树的介绍与实现

88 阅读6分钟

红黑树(Red-Black Tree)是一种自平衡的二叉查找树(BST)。每个节点包含一个颜色(红色或黑色)。

红黑树的性质

红黑树必须满足以下五个性质:

  1. 节点是红色或黑色:每个节点要么是红色的,要么是黑色的。
  2. 根节点是黑色:树的根节点必须是黑色的。
  3. 叶节点(NIL节点)是黑色的:树中的空节点或叶节点(即不存在子节点的节点)被认为是黑色的。
  4. 红色节点不能相邻:红色节点的两个子节点必须是黑色的。即如果一个节点是红色的,那么它的两个子节点必须是黑色的。
  5. 从每个节点到其所有后代叶节点的路径中,黑色节点的数量必须相同:从根节点到每个叶节点的路径上,必须包含相同数量的黑色节点(即黑色高度相同)。

这些规则保证了树的平衡性,从而在最坏情况下保持 O(log n) 的时间复杂度。

红黑树的基本操作

红黑树的基本操作包括插入、删除和查找。特别是插入和删除需要经过一系列的旋转(左旋和右旋)以及重新着色操作,以维护红黑树的性质。

在红黑树中,插入节点的过程需要保持树的平衡,并且满足红黑树的五个性质。插入操作通常会涉及两个主要步骤:

  1. 插入节点:按照二叉查找树的插入规则插入新节点。
  2. 调整树的平衡:通过旋转和重新着色来恢复红黑树的性质,尤其是红色节点不能相邻(即不能有两个连续的红色节点)。

常见情况的插入节点示例

1. 插入新节点到空树

情况:当红黑树为空时,新插入的节点将成为根节点,并且其颜色为黑色。

例子

插入10B

    10B

2.插入节点的父节点为黑色

情况:由于插入的节点是红色的,当插入节点的父节点是黑色时,不会影响红黑树的平衡,所以: 直接插入无需做自平衡

例子: 插入节点 20

    10B
   /   \
 5R     15B
            \
            20R

此时,红黑树的性质没有违反,因为新节点的父节点是黑色,插入后不需要进行旋转或重新着色。

2. 插入节点的父节点为红色

情况:插入的节点的父节点是红色

2.1 父节点和叔叔节点都为红色

例子

假设有以下红黑树:

      10B
     /   \
  5R      20R
 /   
2R

插入节点 2,根据红色节点不能相邻需要进行调整:

  • 首先,父节点和叔叔节点都变为黑色。
  • 祖父节点 10 变为红色。
  • 如果祖父节点的父节点为黑色则无需处理,如果是红色,那么递归进行调整。

结果:

      10R
     /   \
  5B      20B
 /   
2R    

3. 插入节点的父节点是红色,叔叔节点是黑色,并且插在父亲的左节点

情况:插入的节点的父节点是红色,叔叔节点是黑色,并且插在父亲的左节点(LL型失衡)

例子

假设我们有以下红黑树:

      10B
     /   \
  5B      20B
 /   
2R    

插入节点 1,根据红色节点不能相邻需要进行调整:

  • 首先,父节点变为黑色。
  • 祖父节点 5 变为红色。
  • 进行右旋

结果:

        10B
       /   \
     2B      20B        
    /  \
  1R    5R

4. 插入节点的父节点是红色,叔叔节点是黑色,并且插在父亲的右节点

情况:插入节点的父节点是红色,叔叔节点是黑色,并且插在父亲的右节点(LR失衡

例子

假设我们有以下红黑树:

      10B
     /   
    5R      

插入节点 6,根据红色节点不能相邻需要进行调整:

  • 首先,节点6进行左旋。得到LL型失衡,后续参考LL型失衡处理
// 左旋结果
      10B
     /  
   6R     
   /
  5R
// 最终结果 
   6B     
  /  \
 5R  10R
 

5. 插入节点的父节点是红色,叔叔节点是黑色,并且父亲在祖父节点的右节点

情况:插入节点的父节点是红色,叔叔节点是黑色,并且父亲在祖父节点的右节点,并且插在父亲节点的右节点(RR失衡

例子

假设我们有以下红黑树:

      10B
        \
        20R      

插入节点 25,根据红色节点不能相邻需要进行调整:

  • 首先,父节点变为黑色。
  • 祖父节点 10 变为红色。
  • 进行左旋
// 左旋结果  
   20B     
   /  \
  10R  25R

情况:插入节点的父节点是红色,叔叔节点是黑色,并且父亲在祖父节点的右节点,并且插在父亲节点的左节点(RL失衡

例子

假设我们有以下红黑树:

      10B
        \
        20R      

插入节点 18,根据红色节点不能相邻需要进行调整:

  • 首先,进行右旋变成RR型失衡
  • 参考RR型失衡处理
// 右旋结果  
   20B     
     \
     18R
       \
       20R
// 最终结果  
     18B
    /   \
  10R    20R

实现

接下来,我们通过JavaScript实现一个简单的红黑树。

class Node {
    constructor(data) {
        this.data = data; // 节点存储的数据
        this.color = 'red'; // 节点的颜色,默认为红色
        this.left = null; // 左子节点
        this.right = null; // 右子节点
        this.parent = null; // 父节点
    }
}

class RedBlackTree {
    constructor() {
        this.TNULL = new Node(null); // 叶节点(NIL节点)
        this.TNULL.color = 'black'; // NIL节点是黑色的
        this.root = this.TNULL; // 根节点初始化为NIL
    }

    // 左旋操作
    leftRotate(x) {
        let y = x.right;
        x.right = y.left;
        if (y.left !== this.TNULL) {
            y.left.parent = x;
        }
        y.parent = x.parent;
        if (x.parent === null) {
            this.root = y;
        } else if (x === x.parent.left) {
            x.parent.left = y;
        } else {
            x.parent.right = y;
        }
        y.left = x;
        x.parent = y;
    }

    // 右旋操作
    rightRotate(x) {
        let y = x.left;
        x.left = y.right;
        if (y.right !== this.TNULL) {
            y.right.parent = x;
        }
        y.parent = x.parent;
        if (x.parent === null) {
            this.root = y;
        } else if (x === x.parent.right) {
            x.parent.right = y;
        } else {
            x.parent.left = y;
        }
        y.right = x;
        x.parent = y;
    }

    // 插入修复操作
    fixInsert(k) {
        while (k.parent.color === 'red') {
            if (k.parent === k.parent.parent.left) { 
                let u = k.parent.parent.right; // 叔节点

                if (u.color === 'red') {
                    // 情况1:叔节点是红色
                    u.color = 'black';
                    k.parent.color = 'black';
                    k.parent.parent.color = 'red';
                    k = k.parent.parent;
                } else {
                    if (k === k.parent.right) {
                        // 情况2:当前节点是右子节点
                        k = k.parent;
                        this.leftRotate(k); //LR型
                    }
                    // 情况3:当前节点是左子节点
                    k.parent.color = 'black';
                    k.parent.parent.color = 'red';
                    this.rightRotate(k.parent.parent);
                }
            } else {
                let u = k.parent.parent.left; // 叔节点

                if (u.color === 'red') {
                    // 情况1:叔节点是红色
                    u.color = 'black';
                    k.parent.color = 'black';
                    k.parent.parent.color = 'red';
                    k = k.parent.parent;
                } else {
                    if (k === k.parent.left) {
                        // 情况2:当前节点是左子节点
                        k = k.parent;
                        this.rightRotate(k);
                    }
                    // 情况3:当前节点是右子节点
                    k.parent.color = 'black';
                    k.parent.parent.color = 'red';
                    this.leftRotate(k.parent.parent);
                }
            }
            if (k === this.root) break;
        }
        this.root.color = 'black'; // 根节点始终是黑色
    }

    // 插入节点
    insert(data) {
        let newNode = new Node(data);
        newNode.left = this.TNULL;
        newNode.right = this.TNULL;

        let parent = null;
        let find_node = this.root;

        while (find_node !== this.TNULL) {
            parent = find_node;
            if (newNode.data < find_node.data) {
                find_node = find_node.left;
            } else {
                find_node = find_node.right;
            }
        }

        if (parent === null) {
            this.root = newNode;
            newNode.color = 'black';
            return;
        } else {
            newNode.parent = parent;
            if (newNode.data < parent.data) {
                parent.left = newNode;
            } else {
                parent.right = newNode;
            }
        }

        this.fixInsert(newNode); // 插入后修复红黑树的性质
    }

    // 中序遍历
    inorderHelper(node) {
        if (node !== this.TNULL) {
            this.inorderHelper(node.left);
            console.log(node.data);
            this.inorderHelper(node.right);
        }
    }

    inorder() {
        this.inorderHelper(this.root);
    }
}

// 测试代码
const rbt = new RedBlackTree();
rbt.insert(10);
rbt.insert(20);
rbt.insert(30);
rbt.insert(15);
rbt.insert(25);

console.log('中序遍历结果:');
rbt.inorder(); // 输出:10, 15, 20, 25, 30