Nginx源码分析之ngx_rbtree_t

红黑树的算法确实复杂,本文主要学习如何使用ngx_rbtree_t结构

源码位置:
src/core/ngx_rbtree.h
src/core/ngx_rbtree.c

ngx_rbtree_t是使用红黑树实现的一种关联容器,红黑树实现很复杂,它有几个要素:

1.根节点是黑色
2.NIL哨兵节点是黑色(即叶子节点)
3.所有红色节点的两个子节点都是黑色。
4.任意节点到每个叶子节点的所有简单路径都包含相同数目的黑色节点。

以上就是红黑树的平衡条件,每当插入或者删除节点,都要维持这些平衡条件,因而就有了左旋右旋操作,由于情况众多,所以实现起来很复杂。

本文主要学习nginx中对红黑树的封装,以及用法。

数据结构

typedef ngx_uint_t  ngx_rbtree_key_t;
typedef ngx_int_t   ngx_rbtree_key_int_t;

/* 红黑树节点结构 */
typedef struct ngx_rbtree_node_s  ngx_rbtree_node_t;

struct ngx_rbtree_node_s {
    ngx_rbtree_key_t       key;     /* 节点的键值 */
    ngx_rbtree_node_t     *left;    /* 节点的左孩子 */
    ngx_rbtree_node_t     *right;   /* 节点的右孩子 */
    ngx_rbtree_node_t     *parent;  /* 节点的父亲 */
    u_char                 color;   /* 节点的颜色 */
    u_char                 data;    /* */
};

typedef struct ngx_rbtree_s  ngx_rbtree_t;

typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root,
    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);

/* 红黑树结构 */
struct ngx_rbtree_s {
    ngx_rbtree_node_t     *root;    /* 指向树的根节点 */
    ngx_rbtree_node_t     *sentinel;/* 指向树的叶子节点NIL */
    ngx_rbtree_insert_pt   insert;  /* 添加元素节点的函数指针,解决具有相同键值问题,在初始化时需要指定*/

};

红黑树操作

初始化操作

/* 给节点着色,1表示红色,0表示黑色  */
#define ngx_rbt_red(node)               ((node)->color = 1)
#define ngx_rbt_black(node)             ((node)->color = 0)
/* 判断节点的颜色 */
#define ngx_rbt_is_red(node)            ((node)->color)
#define ngx_rbt_is_black(node)          (!ngx_rbt_is_red(node))
/* 复制某个节点的颜色 */
#define ngx_rbt_copy_color(n1, n2)      (n1->color = n2->color)

/* 节点着黑色的宏定义 */
/* a sentinel must be black */

#define ngx_rbtree_sentinel_init(node)  ngx_rbt_black(node)

/* 初始化红黑树,即为空的红黑树 */
/* tree 是指向红黑树的指针,
 * s 是红黑树的一个NIL节点,
 * i 表示函数指针,决定节点是新增还是替换
 */
#define ngx_rbtree_init(tree, s, i)                                           \
    ngx_rbtree_sentinel_init(s);                                              \
    (tree)->root = s;                                                         \
    (tree)->sentinel = s;                                                     \
    (tree)->insert = i

插入操作

/* 插入节点 */
/* 插入节点的步骤:
 * 1、首先按照二叉查找树的插入操作插入新节点;
 * 2、然后把新节点着色为红色(避免破坏红黑树性质5);
 * 3、为维持红黑树的性质,调整红黑树的节点(着色并旋转),使其满足红黑树的性质;
 */
void
ngx_rbtree_insert(ngx_thread_volatile ngx_rbtree_t *tree,
    ngx_rbtree_node_t *node)
{
    ngx_rbtree_node_t  **root, *temp, *sentinel;

    /* a binary tree insert */

    root = (ngx_rbtree_node_t **) &tree->root;
    sentinel = tree->sentinel;

    /* 若红黑树为空,则比较简单,把新节点作为根节点,
     * 并初始化该节点使其满足红黑树性质
     */
    if (*root == sentinel) {
        node->parent = NULL;
        node->left = sentinel;
        node->right = sentinel;
        ngx_rbt_black(node);
        *root = node;

        return;
    }

    /* 若红黑树不为空,则按照二叉查找树的插入操作进行
     * 该操作由函数指针提供
     */
    tree->insert(*root, node, sentinel);

    /* re-balance tree */

    /* 调整红黑树,使其满足性质,
     * 其实这里只是破坏了性质4:若一个节点是红色,则孩子节点都为黑色;
     * 若破坏了性质4,则新节点 node 及其父亲节点 node->parent 都为红色;
     */
    while (node != *root && ngx_rbt_is_red(node->parent)) {

        /* 若node的父亲节点是其祖父节点的左孩子 */
        if (node->parent == node->parent->parent->left) {
            temp = node->parent->parent->right;/* temp节点为node的叔叔节点 */

            /* case1:node的叔叔节点是红色 */
            /* 此时,node的父亲及叔叔节点都为红色;
             * 解决办法:将node的父亲及叔叔节点着色为黑色,将node祖父节点着色为红色;
             * 然后沿着祖父节点向上判断是否会破会红黑树的性质;
             */
            if (ngx_rbt_is_red(temp)) {
                ngx_rbt_black(node->parent);
                ngx_rbt_black(temp);
                ngx_rbt_red(node->parent->parent);
                node = node->parent->parent;

            } else {
                /* case2:node的叔叔节点是黑色且node是父亲节点的右孩子 */
                /* 则此时,以node父亲节点进行左旋转,使case2转变为case3;
                 */
                if (node == node->parent->right) {
                    node = node->parent;
                    ngx_rbtree_left_rotate(root, sentinel, node);
                }

                /* case3:node的叔叔节点是黑色且node是父亲节点的左孩子 */
                /* 首先,将node的父亲节点着色为黑色,祖父节点着色为红色;
                 * 然后以祖父节点进行一次右旋转;
                 */
                ngx_rbt_black(node->parent);
                ngx_rbt_red(node->parent->parent);
                ngx_rbtree_right_rotate(root, sentinel, node->parent->parent);
            }

        } else {/* 若node的父亲节点是其祖父节点的右孩子 */
            /* 这里跟上面的情况是对称的,就不再进行讲解了
             */
            temp = node->parent->parent->left;

            if (ngx_rbt_is_red(temp)) {
                ngx_rbt_black(node->parent);
                ngx_rbt_black(temp);
                ngx_rbt_red(node->parent->parent);
                node = node->parent->parent;

            } else {
                if (node == node->parent->left) {
                    node = node->parent;
                    ngx_rbtree_right_rotate(root, sentinel, node);
                }

                ngx_rbt_black(node->parent);
                ngx_rbt_red(node->parent->parent);
                ngx_rbtree_left_rotate(root, sentinel, node->parent->parent);
            }
        }
    }

    /* 根节点必须为黑色 */
    ngx_rbt_black(*root);
}

删除操作

/* 删除节点 */
void
ngx_rbtree_delete(ngx_thread_volatile ngx_rbtree_t *tree,
    ngx_rbtree_node_t *node)
{
    ngx_uint_t           red;
    ngx_rbtree_node_t  **root, *sentinel, *subst, *temp, *w;

    /* a binary tree delete */

    root = (ngx_rbtree_node_t **) &tree->root;
    sentinel = tree->sentinel;

    /* 下面是获取temp节点值,temp保存的节点是准备替换节点node ;
     * subst是保存要被替换的节点的后继节点;
     */

    /* case1:若node节点没有左孩子(这里包含了存在或不存在右孩子的情况)*/
    if (node->left == sentinel) {
        temp = node->right;
        subst = node;

    } else if (node->right == sentinel) {/* case2:node节点存在左孩子,但是不存在右孩子 */
        temp = node->left;
        subst = node;

    } else {/* case3:node节点既有左孩子,又有右孩子 */
        subst = ngx_rbtree_min(node->right, sentinel);/* 获取node节点的后续节点 */

        if (subst->left != sentinel) {
            temp = subst->left;
        } else {
            temp = subst->right;
        }
    }

    /* 若被替换的节点subst是根节点,则temp直接替换subst称为根节点 */
    if (subst == *root) {
        *root = temp;
        ngx_rbt_black(temp);

        /* DEBUG stuff */
        node->left = NULL;
        node->right = NULL;
        node->parent = NULL;
        node->key = 0;

        return;
    }

    /* red记录subst节点的颜色 */
    red = ngx_rbt_is_red(subst);

    /* temp节点替换subst 节点 */
    if (subst == subst->parent->left) {
        subst->parent->left = temp;

    } else {
        subst->parent->right = temp;
    }

    /* 根据subst是否为node节点进行处理 */
    if (subst == node) {

        temp->parent = subst->parent;

    } else {

        if (subst->parent == node) {
            temp->parent = subst;

        } else {
            temp->parent = subst->parent;
        }

        /* 复制node节点属性 */
        subst->left = node->left;
        subst->right = node->right;
        subst->parent = node->parent;
        ngx_rbt_copy_color(subst, node);

        if (node == *root) {
            *root = subst;

        } else {
            if (node == node->parent->left) {
                node->parent->left = subst;
            } else {
                node->parent->right = subst;
            }
        }

        if (subst->left != sentinel) {
            subst->left->parent = subst;
        }

        if (subst->right != sentinel) {
            subst->right->parent = subst;
        }
    }

    /* DEBUG stuff */
    node->left = NULL;
    node->right = NULL;
    node->parent = NULL;
    node->key = 0;

    if (red) {
        return;
    }

    /* 下面开始是调整红黑树的性质 */
    /* a delete fixup */

    /* 根据temp节点进行处理 ,若temp不是根节点且为黑色 */
    while (temp != *root && ngx_rbt_is_black(temp)) {

        /* 若temp是其父亲节点的左孩子 */
        if (temp == temp->parent->left) {
            w = temp->parent->right;/* w为temp的兄弟节点 */

            /* case A:temp兄弟节点为红色 */
            /* 解决办法:
             * 1、改变w节点及temp父亲节点的颜色;
             * 2、对temp父亲节的做一次左旋转,此时,temp的兄弟节点是旋转之前w的某个子节点,该子节点颜色为黑色;
             * 3、此时,case A已经转换为case B、case C 或 case D;
             */
            if (ngx_rbt_is_red(w)) {
                ngx_rbt_black(w);
                ngx_rbt_red(temp->parent);
                ngx_rbtree_left_rotate(root, sentinel, temp->parent);
                w = temp->parent->right;
            }

            /* case B:temp的兄弟节点w是黑色,且w的两个子节点都是黑色 */
            /* 解决办法:
             * 1、改变w节点的颜色;
             * 2、把temp的父亲节点作为新的temp节点;
             */
            if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
                ngx_rbt_red(w);
                temp = temp->parent;

            } else {/* case C:temp的兄弟节点是黑色,且w的左孩子是红色,右孩子是黑色 */
                /* 解决办法:
                 * 1、将改变w及其左孩子的颜色;
                 * 2、对w节点进行一次右旋转;
                 * 3、此时,temp新的兄弟节点w有着一个红色右孩子的黑色节点,转为case D;
                 */
                if (ngx_rbt_is_black(w->right)) {
                    ngx_rbt_black(w->left);
                    ngx_rbt_red(w);
                    ngx_rbtree_right_rotate(root, sentinel, w);
                    w = temp->parent->right;
                }

                /* case D:temp的兄弟节点w为黑色,且w的右孩子为红色 */
                /* 解决办法:
                 * 1、将w节点设置为temp父亲节点的颜色,temp父亲节点设置为黑色;
                 * 2、w的右孩子设置为黑色;
                 * 3、对temp的父亲节点做一次左旋转;
                 * 4、最后把根节点root设置为temp节点;*/
                ngx_rbt_copy_color(w, temp->parent);
                ngx_rbt_black(temp->parent);
                ngx_rbt_black(w->right);
                ngx_rbtree_left_rotate(root, sentinel, temp->parent);
                temp = *root;
            }

        } else {/* 这里针对的是temp节点为其父亲节点的左孩子的情况 */
            w = temp->parent->left;

            if (ngx_rbt_is_red(w)) {
                ngx_rbt_black(w);
                ngx_rbt_red(temp->parent);
                ngx_rbtree_right_rotate(root, sentinel, temp->parent);
                w = temp->parent->left;
            }

            if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
                ngx_rbt_red(w);
                temp = temp->parent;

            } else {
                if (ngx_rbt_is_black(w->left)) {
                    ngx_rbt_black(w->right);
                    ngx_rbt_red(w);
                    ngx_rbtree_left_rotate(root, sentinel, w);
                    w = temp->parent->left;
                }

                ngx_rbt_copy_color(w, temp->parent);
                ngx_rbt_black(temp->parent);
                ngx_rbt_black(w->left);
                ngx_rbtree_right_rotate(root, sentinel, temp->parent);
                temp = *root;
            }
        }
    }

    ngx_rbt_black(temp);
}

插入函数

上文提到nginx提供一种机制自定义插入函数解决key值冲突,不过nginx提供几个默认的供我们使用:

// ngx_rbtree.h

//root是容器指针
//node是待插入节点指针
//sentinel是哨兵节点
//向红黑树插入节点,每个节点key都是唯一的
void ngx_rbtree_insert_value(ngx_rbtree_node_t *root, ngx_rbtree_node_t *node,
    ngx_rbtree_node_t *sentinel);

//向红黑书插入数据节点,每个数据节点的关键字表示时间或者时间差
void ngx_rbtree_insert_timer_value(ngx_rbtree_node_t *root,
    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);

另外在ngx_string.h中还声明了处理ngx_str_t插入时的函数

//处理ngx_str_t类型时的插入处理key的函数
void
ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{
    ngx_str_node_t      *n, *t;
    ngx_rbtree_node_t  **p;

    for ( ;; ) {

        n = (ngx_str_node_t *) node;
        t = (ngx_str_node_t *) temp;

    //首先比较key
        if (node->key != temp->key) {

            p = (node->key < temp->key) ? &temp->left : &temp->right;

        //其次比较字串长度
        } else if (n->str.len != t->str.len) {

            p = (n->str.len < t->str.len) ? &temp->left : &temp->right;

        //最后比较字符
        } else {
            p = (ngx_memcmp(n->str.data, t->str.data, n->str.len) < 0)
                 ? &temp->left : &temp->right;
        }

        if (*p == sentinel) {
            break;
        }

        temp = *p;
    }

    *p = node;
    node->parent = temp;
    node->left = sentinel;
    node->right = sentinel;
    ngx_rbt_red(node);
}

测试代码

主要测试如何使用ngx_rbtree_t容器

#include <stdio.h>  
#include <string.h>  
#include "ngx_config.h"  
#include "nginx.h"  
#include "ngx_conf_file.h"  
#include "ngx_core.h"  
#include "ngx_string.h"  
#include "ngx_palloc.h"  
#include "ngx_list.h"  
#include "ngx_queue.h"
#include "ngx_rbtree.h"

volatile ngx_cycle_t *ngx_cycle;  
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log,  
    ngx_err_t err, const char *fmt, ...)  
{  
}  

typedef struct
{
    ngx_rbtree_node_t node;
    ngx_str_t name;
}StuName;

int main(int argc,char**argv)
{
    ngx_rbtree_t rbtree;
    ngx_rbtree_node_t sentinel;

    ngx_rbtree_init(&rbtree,&sentinel,ngx_str_rbtree_insert_value);
    StuName names_[5];
    ngx_str_set(&names_[0].name,"zhang");
    ngx_str_set(&names_[1].name,"zhang333");
    ngx_str_set(&names_[2].name,"zhang222");
    ngx_str_set(&names_[3].name,"zhangg");
    ngx_str_set(&names_[4].name,"zhang");
    int i;
    for(i=0;i<5;++i)
    {
        names_[i].node.key=i;
        if(i==1)
            names_[i].node.key=2;
        if(i==3)
            names_[i].node.key=4;
        ngx_rbtree_insert(&rbtree,&names_[i].node);
    }

    ngx_str_t word=ngx_string("zhang222");
    uint32_t hash=2;

//检索    
    StuName *lookup =(StuName*)ngx_str_rbtree_lookup(&rbtree,&word,hash);

    if(NULL!=lookup)
        printf("name: %s key: %d\r\n",lookup->name.data,lookup->node.key);



    while(rbtree.root!=&sentinel)
    {
        ngx_rbtree_node_t *tmp=ngx_rbtree_min(rbtree.root,&sentinel);
        StuName * info = (StuName*)tmp;
        printf("name: %s key: %d\r\n",info->name.data,info->node.key);
        ngx_rbtree_delete(&rbtree,tmp);
    }
    return 0;
}

测试结果:
这里写图片描述

参考

深入理解nginx
Nginx 红黑树结构 ngx_rbtree_t

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值