红黑树

1 摘要

红黑树(Red Black Tree) 是一种自平衡二叉搜索树,它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。红黑树确保没有一条路径会比其他路径长出两倍。这个树大致上是平衡的,因此,插入、删除和查找操作在最坏情况下都是高效的。

2 性质

在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  • 性质1. 每个结点是红色或黑色
  • 性质2. 根结点是黑色
  • 性质3. 每个叶子结点(NIL节点,空节点)是黑色
  • 性质4. 每个红色结点的两个子结点是黑色(子结点是红色父结点一定是黑色)
  • 性质5. 从任意结点到其每个叶子结点的路径包含相同数目的黑色结点

结论(证明可查阅其他资料,这里不讨论):一棵有n个结点的红黑树的高度至多为为2lg(n+1)

数据结构定义

#include <stdio.h>
#include <stdlib.h>

//定义颜色类型
typedef enum Color
{
    RED = 0,
    BLACK = 1
}Color;

//定义结点结构
typedef struct Node //
{
    struct Node *parent;
    struct Node *left;
    struct Node *right;
    int value;
    Color color;
}Node, *Tree;

/*
 方便处理边界,结点NIL替代NULL
 所有空都指向它,可以看成外部结点
 */
Node *NIL=NULL;

3 旋转

当进行插入或删除操作时,可能导致新树不满足红黑树的性质,此刻需要适当的旋转来完成

左旋右旋示意图

3.1 左旋

  • 过程:x的右孩子指向y的左孩子,y的父结点指向x的父结点,y的左孩子指向x

code:

//左旋:将x的右子树y旋转成x的父母
void LeftRotate(Tree T, Node *x)
{
    if(x->right != NIL) {
        Node *y=x->right;
        //1.修改x的右孩子与y的左孩子的父结点的指针
        x->right=y->left;
        if(y->left != NIL) {
            y->left->parent=x;
        }
        //2.修改y的父结点与x父结点的孩子结点的指针
        y->parent=x->parent;
        if(x->parent == NIL){
            T=y;
        }else{
            if(x == x->parent->left) {
                x->parent->left=y;
            }else{
                x->parent->right=y;
            }
        }
        //3.修改y的左孩子与x的父结点的指针
        y->left=x;
        x->parent=y;
    } else {
        printf("can't execute left rotate due to null right child/n");
    }
}

3.2 右旋

  • 过程:y的左孩子指向x的右孩子,x的父结点指向y的父结点,x的右孩子指向y

code:

//右旋:将x的左子树y旋转成x的父母
void RightRotate(Tree T, Node *x)
{
    if( x->left != NIL ) {
        Node *y=x->left;
        //1.修改x的左孩子与y的右孩子的父结点的指针
        x->left=y->right;
        if( y->right != NIL ) {
            y->right->parent=x;
        }
        //2.修改y的父结点与x父结点的孩子结点的指针
        y->parent=x->parent;
        if( x->parent == NIL ) {
            T=y;
        } else {
            if(x == x->parent->left) {
                x->parent->left=y;
            } else {
                x->parent->right=y;
            }
        }
        //3.修改y的右孩子与x的父结点的指针
        y->right=x;
        x->parent=y;
    } else {
        printf("can't execute right rotate due to null left child/n");
    }
}

4 插入

4.1 插入过程

红黑树的插入过程是在二叉搜索树插入的基础上改进的,具体过程是先按照二叉搜索树的插入过程插入并标记红色,然后对插入的结点进行调整

code:

void Insert(Tree T, int val)
{
    if(T == NULL) {
        T=(Tree)malloc(sizeof(Node));
        NIL=(Node*)malloc(sizeof(Node));
        NIL->color=BLACK;
        T->left=NIL;
        T->right=NIL;
        T->parent=NIL;
        T->value=val;
        T->color=BLACK;
    }else{
        Node *x=T;
        Node *p=NIL;
        while(x != NIL) {
            p=x;
            if(val < x->value){
                x=x->left;
            }else if(val > x->value){
                x=x->right;
            }else{
                printf("duplicate value %d/n",val);
                return;
            }
        }
        x=(Node*)malloc(sizeof(Node));
        x->color=RED;
        x->left=NIL;
        x->right=NIL;
        x->parent=p;
        x->value=val;
        if(val < p->value){
            p->left = x;
        }else{
            p->right = x;
        }
        //从x开始调整
        InsertFixup(T, x);
    }
}

4.2.调整过程

4.2.1 分析

当插入一个新的结点(红色),至多违反一个性质,可能违反性质2或性质4。 ① 如果违反性质2,新增的结点z必定是根,此树只有一个结点,直接将根结点置黑即可。 ② 如果违反性质4,此时z与其父结点都为红色,根据红黑树的性质,z的祖父结点一定是黑色。我们再根据z的父结点是z的祖父结点的左孩子还是右孩子进行讨论。由于左右是对称的,这里只讨论z的父结点是z的祖父的左孩子进行讨论,分为3种情况:

  • 情况1:z的叔叔结点y是RED
    处理:将y与z的父结点着为BLACK,z的祖父着为红色,此时z的祖父可能违反性质3,将z上移至祖父结点
情况1
  • 情况2:z的叔叔结点y是BLACK,z是右孩子
    处理:将z上移到父结点,对z左旋,转化为情况3
情况2
  • 情况3:z的叔叔结点y是BLACK,z是左孩子
    处理:将z的父结点着为BLACK,z的祖父结点着为RED,对z的祖父结点右旋
情况3
4.2.2 过程模拟

插入过程请点击这里

code:

void InsertFixup(Tree T, Node *z) {
    Node *y;
    //插入结点的父结点为红色时,违反性质4
    while( z->parent->color == RED ) {
        if( z->parent->parent->left == z->parent ) {
            //y指向z的叔叔结点
            y=z->parent->parent->right;
            /*
             情况1:如果y为RED,将y与z的父结点着为BLACK,z的祖父着为红色,此时z的祖父可能违反性质3,将z上移至祖父结点
             */
            if(y->color == RED) {
                y->color=BLACK;
                z->parent->color=BLACK;
                z->parent->parent->color=RED;
                z=z->parent->parent;
            } else {
                /*
                 情况2:如果y为BLACK,且z是右孩子,将z上移到父结点,对z左旋,转化为情况3
                 */
                if(z == z->parent->right) {
                    z=z->parent;
                    LeftRotate(T, z);
                }
                /*
                 情况3:如果y为BLACK,且z是左孩子,将z的父结点着为BLACK,z的祖父结点着为RED,对z的祖父结点右旋
                 */
                z->parent->color=BLACK;
                z->parent->parent->color=RED;
                RightRotate(T,z->parent->parent);
            }
        } else {
            //对称,左右替换即可
            y=z->parent->parent->left;
            if( y->color == RED) {
                y->color=BLACK;
                z->parent->color=BLACK;
                z->parent->parent->color=RED;
                z=z->parent->parent;
            } else {
                if( z == z->parent->left) {
                    z=z->parent;
                    RightRotate(T,z);
                }
                z->parent->color=BLACK;
                z->parent->parent->color=RED;
                LeftRotate(T,z->parent->parent);
            }
        }
    }
    T->color=BLACK;
}

5 删除

5.1 删除过程

红黑树的删除过程同样是在二叉搜索树删除的基础上改进的。二叉搜索树的删除过程分为3种情况:①无左右孩子 ②有左孩子或右孩子 ③既有左孩子又有右孩子。在删除过程中,如果删除的结点为RED,并没有破坏红黑树的性质;如果删除的结点为BLACK,破坏了红黑树的性质,需要调整,从删除结点y的唯一孩子结点x或是NIL处开始调整(这里做了转换,实际删除的是右子树最小结点或左子树最大结点)

code:

//右子树最小结点
Node* Successor(Tree T, Node *x)
{
    if(x->right != NIL) {
        Node *p=x->right;
        while( p->left != NIL ) {
            p=p->left;
        }
        //右孩子中最小的结点
        return p;
    }
    return x;
}

void Delete(Tree T, Node *z) {
    Node *y;//指向将要被删除的结点
    Node *x;//指向将要被删除的结点的唯一儿子
    //如果有一个子结点为NIL,删除当前结点
    if(z->left == NIL || z->right == NIL){
        y=z;
    } else {
        //删除左子树的最大结点或右子树最小结点
        y=Successor(T, z);//这里是右子树最大结点
    }
    
    //开始删除
    if(y->left != NIL) {
        x=y->left;
    } else {
        x=y->right;
    }
    if (x != NIL){
        x->parent=y->parent;
    }
    if( y->parent == NIL ) {
        T=x;
    } else {
        if( y == y->parent->left ) {
            y->parent->left=x;
        } else {
            y->parent->right=x;
        }
    }
    //交换值
    if( y != z ) {
        z->value=y->value;
    }
    //如果删除的是黑色结点,进行调整
    if( y->color == BLACK ) {
        //没有修改y结点,任意属性的值,可以使用
        DeleteFixup(T,NIL!=x ? x:y);
    }
}

5.2 调整过程

5.2.1 分析

如果被删除结点y是黑色会产生的问题:①如果y是根,y的红色孩子成为新根,违反性质2。 ②如果x与y的父结点都是红色,违反性质4。 ③删除y会导致之前包含y的任意路径上的黑色结点少1,违反性质5。
恢复过程需要根据x是左孩子还是右孩子进行恢复,由于左右是对称的,这里只讨论x是左孩子的恢复过程

具体过程如下:从x结点开始循环,直到:

  • ① x指向一个RED结点,此时将x着为BLACK
  • ② x指向根,x若为RED,着为BLACK
  • ③ 修改颜色 & 进行旋转

循环过程中,w为x的兄弟结点,由于x处之前删除过黑色结点,所以w不可能为NIL,这里分为4种情况

  • 情况1:x的兄弟w为RED
    处理:由于x为BLACK且删除了一个BLACK结点,所以w必有BLACK孩子。此时将w着为BLACK,x父结点着为RED,对x的父结点左旋。x的新兄弟结点w是BLACK,情况1转换为情况2、3或4
情况1
  • 情况2:x的兄弟w为BLACK,且w的两个孩子都为BLACK
    处理:w着为RED,x上移至父结点
情况2
  • 情况3:x的兄弟w为BLACK,且w的左孩子为RED,右孩子为BLACK
    处理:交换w与其左孩子的color,对w进行右旋。旋转后x的新兄弟w是一个有RED右孩子的BLACK结点,转换成情况4
情况3
  • 情况4:x的兄弟w为BLACK,且w的右孩子为RED
    处理:将w着为x父结点的颜色,x父结点着为BLACK,w右孩子着为BLACK,对x的父结点左旋,x设为根结点(结束调整)
情况4
5.2.2 过程模拟

插入过程请点击这里

code:

void DeleteFixup(Tree T, Node *x) {
    //x是RED直接跳出
    while( x != T && x->color == BLACK ) {
        //x为左子树
        if(x == x->parent->left) {
            //w为x的兄弟结点
            Node *w=x->parent->right;
            /*
             情况1:如果w为RED,由于x为BLACK且删除了一个BLACK结点,所以w必有BLACK孩子。
             此时将w着为BLACK,x父结点着为RED,对x的父结点左旋。x的新兄弟结点w是BLACK,情况1转换为情况2、3或4
            */
            if(w->color == RED){
                w->color=BLACK;
                x->parent->color=RED;
                LeftRotate(T, x->parent);
                w=x->parent->right;
            }
            /*
             情况2:如果w为BLACK,w的左右孩子为BLACK。w着为RED,x上移至父结点
             */
            if(w->left->color == BLACK && w->right->color == BLACK) {
                w->color=RED;
                x=x->parent;
            } else {
                /*
                 情况3:如果w为BLACK,w的左孩子为RED,右孩子为BLACK。
                 交换w与其左孩子的color,对w进行右旋。旋转后x的新兄弟w是一个有RED右孩子的BLACK结点,转换成情况4
                 */
                if(w->right->color == BLACK) {
                    w->color=RED;
                    w->left->color=BLACK;
                    RightRotate(T, w);
                    w=x->parent->right;
                }
                /*
                 情况4:如果w为BLACK,w的右孩子为RED。
                 将w着为x父结点的颜色,x父结点着为BLACK,w右孩子着为BLACK,对x的父结点左旋,x设为根结点
                 */
                w->color=x->parent->color;
                x->parent->color=BLACK;
                w->right->color=BLACK;
                LeftRotate(T,x->parent);
                x=T;
            }
        } else {
            //与前面对称,不再详述
            Node *w=x->parent->left;
            if(w->color == RED) {
                w->color=BLACK;
                x->parent->color=RED;
                RightRotate(T, x->parent);
                w=x->parent->left;
            }
            if(w->left->color == BLACK && w->right->color == BLACK) {
                w->color=RED;
                x=x->parent;
            } else {
                if(w->left->color == BLACK) {
                    w->color=RED;
                    w->right->color=BLACK;
                    LeftRotate(T, w);
                    w=x->parent->left;
                }
                w->color=x->parent->color;
                x->parent->color=BLACK;
                w->left->color=BLACK;
                RightRotate(T,x->parent);
                x=T;
            }
        }
    }
    x->color=BLACK;
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 红黑树为一棵二叉搜索树,它为每个结点增加一个变量存储结点颜色,利用结点颜色对树的形状进行约束,使其近似平衡(并非完...
    LRC_cheng阅读 456评论 0 1
  • 一. 算法之变,结构为宗 计算机在很多情况下被应用于检索数据,比如航空和铁路运输业的航班信息和列车时刻表的查询,都...
    Leesper阅读 6,693评论 13 42
  • 原文链接 二叉查找树 由于红黑树本质上就是一棵二叉查找树,所以在了解红黑树之前,咱们先来看下二叉查找树。 二叉查找...
    非典型程序员阅读 2,784评论 2 5
  • 最近花了些时间重拾数据结构的基础知识,先尝试了红黑树,花了大半个月的时间研究其原理和实现,下面是学习到的知识和一些...
    hoohack阅读 1,478评论 8 30
  • 数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树。算法的性能取决于树的形状,而树的形状取决于...
    sunhaiyu阅读 7,579评论 4 32