浅谈红黑树(C语言代码实现)

定义:

我们先来看看《算法导论》中的红黑树的定义:“红黑树是许多‘平衡’搜索树的一种,可以保证在最坏的情况下基本动态集合操作的时间复杂度为O(lgn)。”

性质:

红黑树的性质如下:

1、每个节点是红色的,或者是黑色的。

2、根节点和叶子节点是黑色的。

3、如果一个节点是红色的,那么它的父节点和子节点必须是黑色的。

4、对于每一个节点来说,从该节点到叶子节点的简单路径上,所包含的黑色节点的数量必须一致。

节点属性:

红黑树每个节点的基本属性值有五个:

1、color:表示该节点的颜色属性。

2、data:表示该节点的数据域。

3、parent:表示该节点的父节点。

4、left_child:表示该节点的左子结点。

5、right_child:表示该节点的右子结点。

以下代码的红黑树节点属性信息:

typedef struct rbnode {
    int           data   ;   
    int           color  ;   
    struct rbnode *left_child     ;
    struct rbnode *right_child     ;
    struct rbnode *parent      ;   
} Rbnode;

typedef Rbnode *Rbtree;

插入操作:

插入操作包含以下三个步骤:

1、查找操作。为待插入节点寻找合适的位置。

红黑树的节点的数据域的规律满足平衡二叉树的节点数据与的规律,也就是某节点的左子结点的数据域小于该节点的数据域,同时该节点的右子结点的数据域大于该节点的数据域。

根据以上数据域的规律可以找到带插入节点的位置,若待插入节点的数据域已经存在于树中,可以选择继续插入,也可以直接返回,该处,我选择若存在就直接返回,若该节点的数据域在该树中不存在,就返回查找到的待测节点的父节点。

代码如下:

static Rbnode *rbtree_search_auxilary(Rbtree root, int data, Rbnode **parent)

{

Rbnode *node = NULL, *sparent;

if(NULL == root){

return NULL;

}

node= root;

while(node){

sparent = node->parent;

if(data == node->data){

return node;

}else if(data > node->data){

node = node->right_child;

}else{

node = node->left_child;

}

}

if(parent){

*parent = sparent;

}

return NULL;

}

2、插入待测节点操作。

由性质4可以得出,为了使新插入节点尽量不破坏红黑树的平衡,新插入节点的颜色必须为红色。

代码如下:

Rbtree rbtree_insert(Rbtree root, int data)
{
    Rbtree tree  = NULL;
    Rbnode *node =  NULL, *parent = NULL;

// analyse whether newnode‘s data in this tree, if yes, you need‘t insert it.
    if(root){
        node = rbtree_search_auxilary(root, data, &parent);
        if(node){
            printf("%d already in this tree\n", data);
            return root;
        }
    }
    // insert data in tree;
    node = buy_node(); // function ‘buy_node‘ just malloc a space for new_node;
    node->data = data;
    node->color = RED;
    node->parent = parent;
    node->left_child = node->right_child = NULL;

if(parent){
        if(parent->left_child){
            parent->left_child = node;
        }else{
            parent->right_child = node;
        }
    }else{
        root = node;
    }

return rbtree_insert_rebalance(root, node);
}

3、平衡红黑树操作。

若新插入节点的父节点为黑色节点,那么该树满足红黑树的性质,不需要调整。若新插入节点的父节点为红色节点,此时违背了红黑树的性质3,需要进一步平衡红黑树。

平衡插入操作有五种情况:

1.父节点P,叔叔节点U,都是红色;
    2.父节点P是红色,叔叔节点U是黑色或者NULL,且N为右孩子
    3.父节点P是红色,叔叔节点U是黑色或者NULL,且N为左孩子

代码如下:

static Rbtree rbtree_insert_rebalance(Rbtree root, Rbnode *node)
{
    Rbnode *gparent = NULL, *parent = NULL, *uncle = NULL;

if(NULL == root || NULL == node){
        return NULL;
    }

while((parent = node->parent) && RED == parent->color){

gparent = parent->parent;

if(gparent){
            if(parent == gparent->left_child){
                uncle = gparent->right_child;
                // case 1,uncle is red;
                if(uncle && RED == uncle->color){
                    parent->color = BLACK;
                    uncle->color = BLACK;
                    gparent->color = RED;
                    node = gparent;
                }else{

/*case2 & case3, uncle is black.

case2: if new_node is parent‘s right_node, becase parent is gparent‘s

left_child, this case belongs to inner_insert, so, it is need

double_rotate, right_rotate -> left_rotate.

case 3: new_node is parent;s left_node, its belongs to outer_insert,

just need left_rotate.

accrounding foregoing analysis, case2 contains case3.*/

if(node == parent->right_child){
                        root = rbtree_rotate_left(root, parent);
                        swap(&parent, &node, sizeof(parent));
                    }

parent->color = BLACK;
                    gparent->color = RED;
                    root = rbtree_rotate_right(root, gparent);
                }
            }else{

// the same as foregoing operating.

uncle = gparent->left_child;

if(uncle && RED == uncle->color){
                    uncle->color = BLACK;
                    parent->color = BLACK;
                    gparent->color = RED;
                    node = gparent;
                }else{
                    if(node == parent->left_child){
                        root = rbtree_rotate_right(root, parent);
                        swap(&parent, &node, sizeof(parent));
                    }
                    parent->color = BLACK;
                    gparent->color = RED;
                    root = rbtree_rotate_left(root, gparent);
                }
            }
        }
    }

root->color = BLACK;
    return root;
}
由于在平衡过程中由旋转(即左旋、右旋)的过程,还有工具函数swap的使用,附上源码:

旋转的过程可能是我们最头疼的问题,因为搞不懂指针的指向,在这里,我与大家分享一下小技巧,在旋转的过程中我们一步一步来。

首先,旋转的过程中解决子节点问题。若右旋过程中,节点存在右子结点,将右子结点托管其父节点,作为父节点的左子结点,左旋则相反。

其次,旋转过程中解决父节点的问题。无论左旋还是右旋其父节点,均都会作为该旋转节点的子节点。

最后,解决节点本身的问题。节点的父节点是哪个?其父节点为该节点父节点的父节点,若父节点的父节点不存在,该节点为root。节点的子节点是哪个?若右旋过程中,父节点作为其右子结点,左子结点不变;左旋相反。

左旋右旋的代码如下:

static Rbtree rbtree_rotate_left(Rbnode *node, Rbtree root)
{
    Rbnode *right = node->right_child;

// child problem
    if(node->right_child = right->left_child){
        right->left_child->parent = node;
    }

// parent problem
    if(right->parent = node->parent){
        if(node == node->parent->right_child){
            node->parent->right_child = right;
        }else{
            node->parent->left_child = right;
        }
    }else{
        root = right;
    }

// self problem
    right->left_child = node;
    node->parent = right;

return root;
}

static Rbtree rbtree_rotate_right(Rbnode *node, Rbtree root)
{
    Rbnode *left = node->left_child;

// child‘s problem
    if(node->left_child = left->right_child){
        left->right_child->parent = node;
    }

// parent‘s problem
    if(left->parent = node->parent){
        if(node == node->parent->right_child){
            node->parent->right_child = left;
        }else{
            node->parent->left_child = left;
        }
    }else{
        root = left;
    }

// self‘s problem
    left->right_child = node;
    node->parent = left;

return root;
}

swap函数源码:

void swap(void *a, void *b, size_t size)
{
    void *tmp;
    tmp = (void *)Malloc(size);
    memcpy(tmp, a, size);
    memcpy(a, b , size);
    memcpy(b, tmp, size);
    free(tmp);
}

删除操作:

删除操作与插入操作类似也分为三步操作:

1、找出待删除节点。其查找方法与插入操作的查找节点的方法一致,不再做过多赘述。

2、若节点存在就删除该节点。

情况一:待删除节点有两个子节点。

情况二:待删除节点有一个子节点或者不存在子节点。

情况一可以转换成情况二,当待删除节点存在两个子节点时,可以用左子树的最大节点或者右子树的最小节点替换该节点,此时,只需要删除左子树的最大节点或者右子树的最小节点即可。

代码如下:

Rbtree rbtree_delete(Rbtree root, int data)
{
    Rbnode *parent = NULL, *child = NULL;
    Rbnode *node   = NULL, *old   = NULL;
    int    color   = RED;

// find the delete-node
    node = rbtree_search_auxilary(root, data, NULL);
    if(!node){
        fprintf(stderr, "%d does not in the tree\n");
        return NULL;
    }
    // remember the delete-node
    old = node;
    /*
     * case 1 : the delete-node has only-one child, you just
     *      let node‘s parent adapt you child;
     * case 2 : the delete-node has two children, you can find
     *      max-left_child or min-right_child to success your
     *      property & support your parent.
     * Then case 2 can switch to case 1.
     */
    // case 2
    if(node->left_child && node->right_child){
        // we choose its min-right_child
        node = node->right_child;
        node = find_min_node(node);

// you should solve the min-right_child‘s child & parent
        child = node->right_child;
        parent = node->parent;
        color = node->color;

// child first
        if(child){
            child->parent = parent;
        }
        // parent sencond
        if(parent){
            if(node == parent->left_child){
                parent->left_child = child;
            }else{
                parent->right_child = child;
            }
        }else{
            root = child;
        }

if(node->parent == old){
            parent = node;
        }

// the use min-right_child to replace the delete-node
        node->parent = old->parent;
        node->right_child = old->right_child;
        node->left_child = old->left_child;
        node->color = old->color;

if(old->parent){
            if(old == old->parent->left_child){
                old->parent->left_child = node;
            }else{
                old->parent->right_child = node;
            }
        }else{
            root = node;
        }
        old->left_child->parent = node;
        if(old->right_child){
            old->right_child->parent = node;
        }
    }else{
       // case 1 : only has one child or has no->child
        if(node->left_child){
            child = node->left_child;
        }else{
            child = node->right_child;
        }
        parent = node->parent;
        color = node->color;

if(child){
            child->parent = parent;
        }

if(parent){
            if(node == parent->left_child){
                parent->left_child = child;
            }else{
                parent->right_child = child;
            }
        }else{
            root = child;
        }
    }

free(old);

if(BLACK == color){
        return rbtree_delete_rebalance(root, child, parent);
    }

return root;
}

3、删除节点后平衡红黑树。

若删除的节点是红色,并没有破坏红黑树的平衡,不需要做平衡操作,若删除节点的黑色节点,是通过该节点的路径比不通过该节点大的路径减少一个黑色节点,违反了红黑树的性质4。

删除操作有以下四种情况:

1.当前节点是黑色,当前兄弟节点为红色(此时父节点和兄弟节点的子节点为黑色);

2.当前节点是黑色,且当前节点的兄弟节点是黑色,并且兄弟节点的两个子节点全为黑色;
      3.当前节点是黑色,兄弟节点是黑色,兄弟节点的左孩子是红色,右孩子是黑色;
      4.当前节点是黑色,兄弟节点是黑色,兄弟节点的右孩子是红色,左孩子颜色任意;

具体平衡操作代码如下:

static Rbtree rbtree_delete_rebalance(Rbtree root, Rbnode *node, Rbnode *parent)
{
     Rbnode *bro = NULL;
     Rbnode *bro_left = NULL, *bro_right = NULL;

while((NULL == node || BLACK == node->color) && node != root){
         if(node == parent->left_child){
             bro = parent->right_child;

// case 1 : bro is Red
             if(bro && RED == bro->color){

bro->color = BLACK;
                 bro->parent = RED;
                 root = rbtree_rotate_left(parent, root);
                 bro = parent->left_child;

}   
                 
             if((!bro->left_child || BLACK == bro->left_child->color) &&
                (!bro->right_child || BLACK == bro->right_child->color)){
                 // case 2 : bro is BLACK & its children are all BLACK
                 bro->color = RED;
                 node = parent;
                 parent = node->parent;
             }else{
                 // case 3 : bro is BLACK & its left_child is RED & bro_right is BLACK
                 if(NULL == bro->right_child || BLACK == bro->right_child->color){
                     if(bro_left = bro->left_child){
                         bro_left->color = BLACK;
                     }
                     bro->color = RED;
                     root = rbtree_rotate_right(bro, root);
                     bro = parent->right_child;
                 }   
                     
                 // case 4 : bro is BLACK & its right_child is RED;
                 bro->color = parent->color;
                 parent->color = BLACK;
                 if(bro->right_child){
                     bro->right_child->color = BLACK;
                 }
                 root = rbtree_rotate_left(parent, root);
                 node = root;
                 break;
             }
         }else{
             bro = parent->left_child;

//  case 1
             if(bro && RED == bro->color){
                 bro->color = BLACK;
                 bro->parent = RED;
                 root = rbtree_rotate_right(parent, root);
                 bro = parent->left_child;
             }

// case 2
             if((!bro->left_child || BLACK == bro->left_child->color) &&
                (!bro->right_child) || BLACK == bro->right_child->color){
                 bro->color = RED;
                 node = parent;
                 parent = node->parent;
             }else{

// case 3
                 if(!bro->right_child || BLACK == bro->right_child->color){
                     if(bro_left = bro->left_child){
                         bro_left->color = BLACK;
                     }
                     bro->color = RED;
                     root = rbtree_insert_rebalance(bro, root);
                     node = parent->left_child;
                 }

// case 4
                 bro->color = parent->color;
                 parent->color = BLACK;
                 if(bro->right_child){
                     bro->right_child->color = BLACK;
                 }
                 root = rbtree_rotate_left(parent,root);
                 node = root;
                 break;
             }
         }
     }

if(node){
         node->color = BLACK;
     }

return root;
}

由于以上代码中牵扯寻找左子树的最大节点,右子树的最小节点,以下是实现源码:

Rbnode *find_min_node(Rbtree root)
{
    Rbnode *node = root;

if(NULL == root){
        return NULL;
    }

while(node->left_child){


node = node->left_child;
    }

return node;
}

Rbnode *find_max_node(Rbtree root)
{
    Rbnode *node = root;

if(NULL == root){
        return NULL;
    }

while(node->right_child){
        node = node->right_child;
    }

return node;
}

以上是本人对红黑树的简单理解,如有不妥之处,欢迎大家指出。

时间: 2024-08-08 13:50:56

浅谈红黑树(C语言代码实现)的相关文章

浅谈红黑树的添加删除操作

红黑树的性质(牢记) 1.每个结点的颜色只能是红色或黑色. 2.根结点必须是黑色的. 3.每个叶子结点都带有两个空的黑色结点(被称为黑哨兵null),如果一个结点n的只有一个左孩子,那么n的右孩子是一个黑哨兵:如果结点n只有一个右孩子,那么n的左孩子是一个黑哨兵. 4.如果一个结点是红的,则它的两个儿子都是黑的.也就是说在一条路径上不能出现相邻的两个红色结点. 5.从任何一个结点到其子孙叶结点的所有路径上包含相同数目的黑结点. 一.红黑树的插入 任何一个即将插入的新结点的初始颜色都为红色.这一点

浅谈红黑树——java面试拦路虎

红黑树相信经过面试官折磨的人都不会太陌生,作为数据结构中较复杂都一种,一直是面试中面试官常用的虐杀手段.废话不多说,直接进入正题. 在了解红黑树之前,先要了解二叉查找数,又叫二叉树.二叉树顾名思义,是一种每个节点最多有两个子节点都树,同时遵循 左节点的值<父节点的值<右节点的值 这样的规律,如下图所示. 它是一种查找次数小于等于树高的数据结构.如图中树有4层,即树高为4,当我们需要查找8时,经过的路线是这样的: 1.8<9,往左查找 2.8>5,往右查找 3.8>7,往右查找

浅谈红黑树

红黑树是什么? 其实也是平衡二叉树,只是给每个节点标了红黑颜色 为什么需要红黑树? 红黑树其实基础还是二叉查找树,只是因为二叉查找树很容易出现不平衡的情况,最坏情况相当于O(n),红黑树和AVL树应运而生,AVL树的话,因为平衡度要求是[-1,1]太严格,所以插入和删除的时候的效率并不是很高,比不上红黑树,但是因为平衡度是[-1,1],所以查找速度比红黑树快,AVL适用于频繁查找,红黑树适用于频繁插入和删除 红黑树五大特性 1,每个节点不是黑色就是红色 2,根节点肯定是黑色 3,叶子节点肯定是黑

2048游戏C语言代码

听说2048游戏实现起来很easy! 所以今天就试了试!确实不太难,要想编的很成功,也不是太容易!有很多细节需要考虑! 下面是我自己设计的代码,估计里面会漏洞百出!希望路过大神能指点一二! #include<stdio.h> #include<stdlib.h> #include<conio.h> #include<time.h> #define WIN 256 // 可以修改决定游戏输赢的值 // 矩阵数组 int num[4][4]={0,0,0,0,0

HTML5 脚本 语言代码 URL 符号实体 ASCII码 颜色

1.HTML<noscript> 标签 <noscript> 标签提供无法使用脚本时的替代内容,比方在浏览器禁用脚本时,或浏览器不支持客户端脚本时. <noscript>元素可包含普通 HTML 页面的 body 元素中能够找到的所有元素. 只有在浏览器不支持脚本或者禁用脚本时,才会显示 <noscript> 元素中的内容: 2.语言代码:http://www.runoob.com/tags/html-language-codes.html 3.URL -

HTML之一语言代码

HTML的lang属性可用于网页或部分网页的语言.这对搜索引擎和浏览器是有帮助的. ISO 639-1语言代码   可以在HTML的lang属性中使用它们. Language---------------------------------ISO Code English en(语言代码) 不同于HTTP Header之Content-Language/Accept-Language 以及meta之<meta http-equiv="Content-Language" conte

编程精粹--编写高质量C语言代码(6):对程序进行逐条跟踪

发现程序错误最好的方法就是执行程序.在程序执行过程中,我们利用我们的眼睛,或者通过我们编写的断言和子系统一致性检查等自动测试的工具来发现错误.虽然断言和子系统检查都很有用,但是如果程序员事先没有想到应该对某些问题进行检查,那么也就无法保证程序没有问题. 程序员可以在代码中设置断点,一步步跟踪代码的运行,观察输入变为输出的过程.程序员测试其程序最好的方法就是对程序进行逐条跟踪,对中间的结果进行认真的查看.对代码进行逐条跟踪是需要时间的,但它同编码比,只是一小部分.一旦逐条地跟踪代码成为习惯后,我们

Latex中插入C语言代码

Latex是一个文本排版的语言,能排版出各种我们想要的效果.而且用代码排版的优点是易于修改板式,因此在文本内容的排版时,Latex应用十分广泛. 当我们需要在Latex中插入代码时,就需要用到 \usepackage{listings} 宏包.例如插入一个简单的C语言代码 #include <stdio.h> int main(int argc, char ** argv) { printf("Hello, world!\n"); return 0; } 要将上面 Hell

Android安全防护之旅---带你把Apk混淆成中文语言代码

一.前言 最近想爆破一个app,没有加壳,简单的使用Jadx打开查看源码,结果把我逗乐了,代码中既然都是中文,而且是一些比较奇葩的中文字句,如图所示: 瞬间感觉懵逼了,这app真会玩,我们知道因为Java语言是支持双字符的,所以可以将包名,类名,变量名,方法名定义成中文,或者其他国家的语言都可以的.所以本身这种做法是不会运行报错的,比如下面我们新建一个Java工程看一下效果: 运行是没有任何问题的.看到这里的时候觉得很好奇,所以就先没去看他的源码了,而是想着怎么实现这种混淆的功能.下面就来介绍一