红黑数之原理分析及C语言实现

1.红黑树简介

 1.1 红黑树概念

  红黑树(Red-Black Tree,简称R-B Tree)是一棵特殊的二叉搜索树(任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值)。

  它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED或者BLACK。通过对一条从根节点到NIL叶节点(指空结点或者下面说的哨兵)的简单路径上各个结点在颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似平衡的。

 1.2 红黑树特征

  1) 每个节点或者是黑色,或者是红色。
  (2) 根节点是黑色。

  (3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
  (4) 如果一个节点是红色的,则它的子节点必须是黑色的。
  (5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

 1.3 红黑树用途 

  红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。对于查找、插入、删除、最大、最小等动态操作的时间复杂度为O(lgn).常见的用途有以下几种:

  • STL(标准模板库)中在set map是基于红黑树实现的。
  • epoll在内核中的实现,用红黑树管理事件块。
  • linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块

2. 红黑树C语言实现

 2.1 树形结构基本实现

  红黑树属于特殊的查找树,因此先对树形结构进行基本讲解。

  首先,树形结构有各个节点组成,节点的描述如下:

struct  BTNode{
    int data;
    };

  节点需要有自己的左右子孩子,因此需要两个指针

typedef struct BTNode{
    int data;
    struct BTNode *lChildstruct BTNode *rChild;
}BiNode;

  对二叉树进行创建

//先序创建

int CreateBiTree(BiTNode **T) { int ch; scanf("%d",&ch); if (ch == -1) { *T = NULL; return 0; } else { *T = (BiTNode *)malloc(sizeof(BiTNode)); //为此节点申请内存空间,返回指针强制转换成BiNode*类型 if (T == NULL) { printf("failed\n"); return 0; } else { (*T)->data = ch; printf("输入%d的左子节点:",ch); CreateBiTree(&((*T)->lChild));   //递归方式存储左子节点数据 printf("输入%d的右子节点:",ch); CreateBiTree((&(*T)->rChild));   //递归方式存储右子节点数据 } } return 1; }

  最开始时对二叉树的遍历难以理解,不过从算法中的递归角度进行理解将会简单很多。

  二叉树先序遍历

void PreOrderBiTree(BiTNode *T)  
{  
    if (T == NULL)  
    {  
        return;  
    }  
    else  
    {  
        printf("%d ",T->data);  
        PreOrderBiTree(T->lChild);  
        PreOrderBiTree(T->rChild);  
    }  
}  

  二叉树中序遍历

void MiddleOrderBiTree(BiTNode *T)  
{  
    if (T == NULL)  
    {  
        return;  
    }  
    else  
    {  
        MiddleOrderBiTree(T->lChild);  
        printf("%d ",T->data);  
        MiddleOrderBiTree(T->rChild);  
    }  
}  

  二叉树后序遍历

void PostOrderBiTree(BiTNode *T)  
{  
    if (T == NULL)  
    {  
        return;  
    }  
    else  
    {  
        PostOrderBiTree(T->lChild);  
        PostOrderBiTree(T->rChild);  
        printf("%d ",T->data);  
    }  
}  

  二叉树深度计算

int TreeDeep(BiTNode *T)  
{  
    int deep = 0;  
    if (T != NULL)  
    {  
        int leftdeep = TreeDeep(T->lChild);  
        int rightdeep = TreeDeep(T->rChild);  
        deep = leftdeep >= rightdeep?leftdeep+1:rightdeep+1;  //从最后节开始,递归逐步后退,每退回一个节点 深度+1
    }  
  
    return deep;  
}  

  二叉树叶子结点个数

int LeafCount(BiTNode *T)  
{  
    static int count;  
    if (T != NULL)  
    {  
        if (T->lChild == NULL && T->rChild == NULL)  
        {  
            count++;  
        }  
  
        LeafCount(T->lChild);  
        LeafCount(T->rChild);  
    }  
  
    return count;  
}  

2.2 红黑树实现

  红黑树首先作为树的结构出现,包含了树形结构的基本特征,操作具有查找、添加、删除。在添加节点时必然导致红黑树的结构不符合红黑树规则,此时将对添加节点后的树进行旋转(左旋和右旋)以保证回复红黑树的特性。

  下面将以红黑树的旋转、添加、删除进行逐步讲解。

  红黑树基本节点定义

#define RED      0    // 红色节点
#define BLACK    1    // 黑色节点

typedef int Type;

// 红黑树的节点
typedef struct RBTreeNode{
    unsigned char color;        // 颜色(RED 或 BLACK)
    Type   key;                    // 关键字(键值)
    struct RBTreeNode *left;    // 左孩子
    struct RBTreeNode *right;    // 右孩子
    struct RBTreeNode *parent;    // 父结点
}Node, *RBTree;

// 红黑树的根
typedef struct rb_root{
    Node *node;
}RBRoot;

  红黑树旋转

    这里写图片描述

  左旋(即上图从右到左)代码实现

static void left_rotate(RBRoot *root, Node *x)
{
    // 设置x的右孩子为y
    Node *y = x->right;

    // 将 “y的左孩子” 设为 “x的右孩子”;
    // 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
    x->right = y->left;
    if (y->left != NULL)
        y->left->parent = x;

    // 将 “x的父亲” 设为 “y的父亲”
    y->parent = x->parent;

    if (x->parent == NULL)
    {
        //tree = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点
        root->node = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点
    }
    else
    {
        if (x->parent->left == x)
            x->parent->left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
        else
            x->parent->right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
    }
    
    // 将 “x” 设为 “y的左孩子”
    y->left = x;
    // 将 “x的父节点” 设为 “y”
    x->parent = y;
}

  右旋(即上图从左向右)代码实现 

static void rbtree_right_rotate(RBRoot *root, Node *y)
{
    // 设置x是当前节点的左孩子。
    Node *x = y->left;

    // 将 “x的右孩子” 设为 “y的左孩子”;
    // 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
    y->left = x->right;
    if (x->right != NULL)
        x->right->parent = y;

    // 将 “y的父亲” 设为 “x的父亲”
    x->parent = y->parent;

    if (y->parent == NULL) 
    {
        //tree = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点
        root->node = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点
    }
    else
    {
        if (y == y->parent->right)
            y->parent->right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
        else
            y->parent->left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
    }

    // 将 “y” 设为 “x的右孩子”
    x->right = y;

    // 将 “y的父节点” 设为 “x”
    y->parent = x;
} 

  红黑树添加

  插入操作需要保证插入数据后红黑树的基本特征不被破坏,红黑树的5个基本特征复习一下:

  1) 每个节点或者是黑色,或者是红色。
  (2) 根节点是黑色。
  (3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
  (4) 如果一个节点是红色的,则它的子节点必须是黑色的。
  (5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

  因此插入操作的关键在于以下几点:

  1. 新插入的节点一定是红色的。(如果是黑色的,会破坏条件5)
  2. 如果新插入的节点的父亲是黑色的,则没有破坏任何性质,那么插入完成。
  3. 如果插入节点的父节点是红色, 破坏了性质4. 故插入算法就是通过重新着色或旋转, 来维持性质

   插入新的数据(红色节点),一定要考虑他的叔叔的情况。并且,插入操作主要是维护颜色来保证树的平衡。将插入操作分为以下几种情况分别考虑:

  case 1. 如果插入的节点是根节点,也就是说初始的红黑树为空,这是最简单的情况,直接将该节点标识成黑色即可。

       

  case 2. 如果新插入的节点不是红黑树的根节点,如果新插入的节点的父节点是黑色的话,那么红黑树是不需要调整的。

         

  case 3. 如果新插入的节点的父节点是红色的话,显然这里违反了红黑树中不能存在父节点和子节点同时为红色的性质。

      叔节点为红色:修改祖父节点为红色,父节点及叔节点改为黑色。祖父节点再视为新加入节点与上层比对,保证红黑树不被破坏。

         

      修改后:

      

      叔节点为黑色:旋转,使父节点占据祖父节点位置,之后交换P与G的颜色:

     

      旋转后:

      

猜你喜欢

转载自www.linuxidc.com/Linux/2017-09/146891.htm