手写简单红黑树

593 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

此文章记录个人学习红黑树时的相关笔记,需要您具备:

  1. 二叉树相关知识
  2. 基本的C语言编程能力

若文章内容对您有所帮助,那将是我的荣幸!

红黑树的基本介绍

  1. 红黑树是一种自平衡二叉查找树,与AVL树(平衡二叉树)相比,红黑树在工程上实现起来更为简单,而且其平衡的代价更低【毕竟不是严格看所有结点的高度差,而是看黑色结点的高度差】。国内有论文指出红黑树平均统计性能强于AVL
  2. 红黑树平衡是以黑色结点的高度作为衡量标准

红黑树使用在什么场景

  • map
  • nginx
  • 定时器
  • 进程调度(cfs)
  • 内存管理(内存块索引: 存储地址 + 内存长度)

红黑树的相关性质

特点

  1. 结点是红色或者黑色
  2. 根节点是黑色
  3. 所有的叶子结点都是黑色(叶子是NIL结点)
  4. 如果一个结点是红的,那么它的两个儿子都是黑的【不可出现连续两个红色结点,即父节点和当前节点不能都是红色的】
  5. 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点*【这句话意思是,红黑树当中,黑高是要完全相同的!】

若不满足这5条特点的,不能称之为红黑树。其中第五点很容易忽略,

e.g

这种是不行的,直接看最左和最右两条线可知,这两条路径上的黑高不一样(黑色结点数量不相等)

红黑树相关操作

旋转

左旋

理论上的流程:

  1. X结点右指针指向b结点【x与y已经断开】
  2. y结点左指针指向x结点【y与B断开】

工程上的流程【树的结点多了个parent,而且还要考虑左右指针是否指向NIL(空节点)】

备注:铅笔为父指针,黑色签字笔为左右子树指针

  1. x的右子树指向y的左子树

    a. 如果y的左子树(b)不为空,b的父指针指向x | 如果y的左子树b为空,不处理

  2. y的父指针指向x的父指针

    a. 若x的parent是根节点【即x是根节点】,那么y就要变成根节点 b. 若x是x父节点的左子树,那么x父节点的左指针指向y c. 若x是x父节点的右子树,那么x父节点的右指针指向y

  3. y的左指针指向x

  4. x的父节点指向y

右旋

理论上的流程:

  1. y结点左指针指向b结点【x与y已经断开】
  2. x结点右指针指向y结点【y与B断开】

工程上的流程【树的结点多了个parent,而且还要考虑左右指针是否指向NIL(空节点)】

备注:铅笔为父指针,黑色签字笔为左右子树指针 本质上,这里的流程和前面左旋的流程一模一样,只是方向完全相反。

  1. y指向x的右子树 a. 如果x的右子树(b)不为空,b的父指针指向y | 如果x的右子树b为空,不处理

  2. x的父指针指向y的父指针【即让x指向parent结点】 a. 若y的parent是根节点【即y是根节点】,那么x就要变成根节点 b. 若y是y父节点的左子树,那么y父节点的左指针指向x c. 若y是y父节点的右子树,那么y父节点的右指针指向x

  3. x的右指针指向y

  4. y的父节点指向x

插入结点

红黑树添加结点后,视添加的结点情况,平衡会被打破。

  1. 根据排序二叉树的方式,将节点按照大小顺序找到具体位置,插入【这种插入肯定实在叶子节点处发生】。所以默认插入结点为红色。
  2. 若插入的节点的父节点是红色结点,那么就需要调整。若是黑色结点,就无需调整。

当插入结点【当前节点】和其父节点都是红色结点时,需要进行调整:

父节点是祖父节点的左孩子

父节点和叔叔结点【叔父结点】均为红色:

插入89。

解决办法就是变色

  1. 父节点变为黑色
  2. 叔叔结点变为黑色
  3. 祖父节点变为红色

但是祖父节点变色之后,如果祖父节点的父节点也是红色,那么就有问题了!

故,需要向上调整。即将当前节点设置为祖父节点,接着对“当前节点”进行操作。

父节点和叔叔结点【叔父结点】为一红一黑

【即叔结点为黑色的】

  • 当前节点是父节点的右孩子【需先将右孩子变为左孩子,再进行操作】

首先以父节点【344】作为支点,进行左旋:

此时将当前节点从【350】变为【344】

则变为执行【当前节点是父节点的左孩子】的流程

  • 当前节点是父节点的左孩子

  1. 将父节点【350】变为黑色
  2. 将祖父节点【359】变为红色
  3. 以祖父节点【359】为支点进行右旋

父节点是祖父节点的右孩子

父节点和叔叔结点【叔父结点】均为红色:

插入【799】

解决办法就是变色

  1. 父节点变为黑色
  2. 叔叔结点变为黑色
  3. 祖父节点变为红色

但是祖父节点变色之后,如果祖父节点的父节点也是红色,那么就有问题了!

故,需要向上调整。即将当前节点设置为祖父节点,接着对“当前节点”进行操作。

父节点和叔叔结点【叔父结点】为一红一黑

【即叔结点为黑色的】

  • 当前节点是父节点的左孩子【需先将左孩子变为右孩子,再进行操作】

首先以父节点【379】作为支点,进行右旋

则变为执行【当前节点是父节点的右孩子】的流程

  • 当前节点是父节点的右孩子

  1. 将父节点【365】变为黑色
  2. 将祖父节点【359】变为红色
  3. 以祖父节点【359】为支点进行左旋

删除节点

未完待续……

手写红黑树代码

#include <iostream>

using namespace std;

typedef int KEY_TYPE;

struct rbtree_node {
    unsigned char color;
    struct rbtree_node *parent;
    struct rbtree_node *right;
    struct rbtree_node *left;

    KEY_TYPE key;
};

struct rbtree {
    rbtree_node *root;
    rbtree_node *nil; // 哨兵
};

// 旋转
// 旋转不涉及到颜色的变化!
void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
    // nullptr --> T->nil
    if (x == T->nil)
        return;

    rbtree_node *y = x->right;

    x->right = y->left;
    // y的左子树不为空
    if (y->left != T->nil) {
        y->left->parent = x;
    }

    y->parent = x->parent;
    if (x->parent == T->nil) {
        // x的parent是根节点
        T->root = y;
    } else if (x == x->parent->left) {
        // x是parent的左子树
        x->parent->left = y;
    } else {
        // x是parent的右子树
        x->parent->right = y;
    }

    y->left = x;
    x->parent = y;
}

void rbtree_right_rotate(rbtree *T, rbtree_node *y) {
    // nullptr --> T->nil
    if (y == T->nil)
        return;

    rbtree_node *x = y->left;

    y->left = x->right;
    // x的左子树不为空
    if (x->left != T->nil) {
        x->left->parent = y;
    }

    x->parent = y->parent;
    if (y->parent == T->nil) {
        // y的parent是根节点
        T->root = x;
    } else if (y == y->parent->left) {
        // y是parent的左子树
        y->parent->left = x;
    } else {
        // y是parent的右子树
        y->parent->right = x;
    }

    x->right = y;
    y->parent = x;
}

#define RED   0
#define BLACK 0

void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
    // 若插入结点的父节点是黑色,则无需处理!
    while (z->parent->color == RED)
    // 插入结点的父节点是红色,需要处理!
    {
        if (z->parent == z->parent->parent->left) {
            // 插入结点的父节点是祖父结点的左孩子
            // 这里的y是叔叔节点
            auto y = z->parent->parent->right;
            // rbtree_node *y = z->parent->parent->right;

            // 叔结点是红色的
            if (y->color == RED) {
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                y->color = BLACK;
                // 从祖父节点开始迭代。
                z = z->parent->parent;
            } else {
                // 特殊情况
                // 叔结点是黑色的,
                if (z == z->parent->right) {
                    z = z->parent;
                    rbtree_left_rotate(T, z);
                }

                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                rbtree_right_rotate(T, z->parent->parent);
            }
        } else {
            auto y = z->parent->parent->left;
            if (y->color == RED) {
                z->parent->color = BLACK;
                y->color = BLACK;
                z->parent->parent->color = RED;

                z = z->parent->parent;
            } else {
                if (z == z->parent->left) {
                    z = z->parent;
                    rbtree_right_rotate(T, z);
                }
                z->parent->color = BLACK;
                z->parent->parent->color = RED;
                rbtree_left_rotate(T, z->parent->parent);
            }
        }
    }
    // 无论如何,根节点必须是黑的。
    T->root->color = BLACK;
}

void rbtree_insert(rbtree *T, rbtree_node *z) {
    rbtree_node *y = T->nil;
    rbtree_node *x = T->root;
    // 类似avl,查找到具体插入点的位置

    // 等于叶子结点时退出循环
    while (x != T->nil) {
        // 留一个y,保留x的父节点
        y = x;

        if (z->key < x->key) {
            x = x->left;
        } else if (z->key > x->key) {
            x = x->right;
        } else {
            // 已经存在这个节点了!不操作
            return;
        }
    }

    // 设置z的父指针
    z->parent = y;
    if (y == T->nil) {
        T->root = z;
    } else if (z->key < y->key) {
        // z在左子树
        // z->parent->left = z;
        y->left = z;
    } else {
        y->right = z;
    }
    // 增加的节点都是默认红色的
    z->color = RED;
    z->left = T->nil;
    z->right = T->nil;

    rbtree_insert_fixup(T, z);
}

void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {
}

// 找后继,属于删除节点里的内容,未完待续……
rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) {
}

// 删除结点,未完待续……
rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {

    rbtree_node *x = T->nil;
    // y是后继节点……
    rbtree_node *y = T->nil;
    if ((z->left == T->nil) || (z->right == T->nil)) {
        y = z;
    } else {
        y = rbtree_successor(T, z);
    }

    if (y->color == BLACK) {
        rbtree_delete_fixup(T, x);
    }
}

技术参考

本文部分技术点出,Linux C/C++服务器直播视频:推荐免费订阅