您的位置:首页 > 运维架构 > Nginx

nginx笔记:红黑树

2014-03-17 23:55 113 查看
      看代码前请先通过这里下载一份wikipedia关于红黑树的介绍,我做了一些批注,结合上面的内容看nginx实现的红黑树要简单一些,不然直接看源码有点头痛。

nginx实现的红黑树源码我做了一些注释,希望对您有点帮助:

ngx_rbtree.h

/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/

#ifndef _NGX_RBTREE_H_INCLUDED_
#define _NGX_RBTREE_H_INCLUDED_

#include <ngx_config.h>
#include <ngx_core.h>

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;
// 节点的颜色,0表示黑色,1表示红色
u_char color;
// 仅1个字节的节点数据。由于表示的空间太小,所
以一般很少使用。
u_char data;
};

typedef struct ngx_rbtree_s ngx_rbtree_t;
//如果不希望出现具有相同key关键字的不同节点再向红黑树添加时出现覆盖原节点的情况就需要实现自有的ngx_rbtree_insert_bt方法
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;
// 指向NIL哨兵节点
ngx_rbtree_node_t *sentinel;
// 表示红黑树添加元素的函数指针,它决定在添加新节点时的行为究竟是替换还是新增
ngx_rbtree_insert_pt insert;//红黑树内部插入函数用于将待插入的节点放在合适的NIL叶子节点处
};

#define ngx_rbtree_init(tree, s, i) \
ngx_rbtree_sentinel_init(s); \
(tree)->root = s; \
(tree)->sentinel = s; \
(tree)->insert = i

void ngx_rbtree_insert(ngx_thread_volatile ngx_rbtree_t *tree,
ngx_rbtree_node_t *node);
void ngx_rbtree_delete(ngx_thread_volatile ngx_rbtree_t *tree,
ngx_rbtree_node_t *node);
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);

#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)

static ngx_inline ngx_rbtree_node_t *
ngx_rbtree_min(ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
{//找到红黑树的最小key只需要遍历至最左
while (node->left != sentinel) {
node = node->left;
}

return node;
}

#endif /* _NGX_RBTREE_H_INCLUDED_ */


ngx_rebtree.c
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/

#include <ngx_config.h>
#include <ngx_core.h>

/*
* The red-black tree code is based on the algorithm described in
* the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
*/

static ngx_inline void ngx_rbtree_left_rotate(ngx_rbtree_node_t **root,
ngx_rbtree_node_t *sentinel, ngx_rbtree_node_t *node);
static ngx_inline void ngx_rbtree_right_rotate(ngx_rbtree_node_t **root,
ngx_rbtree_node_t *sentinel, ngx_rbtree_node_t *node);

void
ngx_rbtree_insert(ngx_thread_volatile ngx_rbtree_t *tree,
ngx_rbtree_node_t *node)//红黑树容器对外提供的插入API
{
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);//将node插入到树中合适的NIL节点处,此时红黑树性质可能被破坏,需要后续重新平衡树

/* re-balance tree */

while (node != *root && ngx_rbt_is_red(node->parent)) {//插入节点node默认是红色
//case1:若插入节点node是新根则直接重绘为黑色即可
//case2:若node的父节点是黑色那么直接插入node即可,case1和case2这两种简单的情形不进入while循环体
//此后的情形都是在node为红色,node->parent为红色的前提下,破坏了红黑树的性质4(红色节点必有两个黑色儿子)从而需要调整
if (node->parent == node->parent->parent->left) {
temp = node->parent->parent->right;//node的叔父节点temp(右儿子)

if (ngx_rbt_is_red(temp)) {//case3:叔父为红色,重绘父节点node->parent和叔父temp为黑色,此时祖父node->parent->parent可能破坏了性质5(节点所有子孙路径具有相同数目的黑色节点),祖父需要重新开始调整
ngx_rbt_black(node->parent);
ngx_rbt_black(temp);
ngx_rbt_red(node->parent->parent);
node = node->parent->parent;//祖父破坏了性质5,从祖父处重新开始调整(祖父作为node从while处开始调整)

} else {//叔父temp为黑色或者缺失为NIL节点
if (node == node->parent->right) {
//case4: 在叔父temp为黑色或缺失前提下,且祖父node->parent->parent、父节点node->parent、node不在一条直线上,这里是祖父的左儿子是node的父节点,父节点的右儿子的node
// 需要从node的父节点处左旋转使得祖父、父亲、儿子node在一条直线上进入case5,这里注意旋转后是原来node和node父节点位置对调了
node = node->parent;//进入case5的是原来的父节点
ngx_rbtree_left_rotate(root, sentinel, node);
}
//case5: node、node->parent、node->parent->parent均在同一直线上,这里满足node->parent->parent->left== node->parent,node->parent->left == node
// 且node和node->parent为红色违背了红黑树性质4,且node的叔父node->parent->right为黑色,祖父node->parent->parent为黑色
// 对祖父和父节点的颜色重绘,在node->parent->parent进行右旋
ngx_rbt_black(node->parent);
ngx_rbt_red(node->parent->parent);
ngx_rbtree_right_rotate(root, sentinel, node->parent->parent);
}

} else {//这里和上面的case是对称的,原理是一样的,不再重述
temp = node->parent->parent->left;

if (ngx_rbt_is_red(temp)) {//case3
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) {//case4
node = node->parent;
ngx_rbtree_right_rotate(root, sentinel, node);
}
//case5
ngx_rbt_black(node->parent);
ngx_rbt_red(node->parent->parent);
ngx_rbtree_left_rotate(root, sentinel, node->parent->parent);
}
}
}

ngx_rbt_black(*root);//对根节点重绘为黑色,当case1插入节点node为新根时该步有用,其他case该步多余但无害处
}

void
ngx_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
ngx_rbtree_node_t *sentinel)//tree->insert(*root, node, sentinel);
{//遍历树temp找到node那个合适的插入NIL位置(p)
ngx_rbtree_node_t **p;

for ( ;; ) {

p = (node->key < temp->key) ? &temp->left : &temp->right;
//若node->key == temp->key时插入到temp->right位置,若想要自行定义key相同时的方法可以重写ngx_rbtree_insert方法

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

temp = *p;
}

*p = node;//p是个NIL就是node带插入的位置,而temp指向p的父节点
node->parent = temp;
node->left = sentinel;
node->right = sentinel;
ngx_rbt_red(node);//插入节点默认是红色
}

void
ngx_rbtree_insert_timer_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
ngx_rbtree_node_t *sentinel)
{//nginx定义的ngx_rbtree_insert_pt方法,和前面的ngx_rbtree_insert_value性质类似,区别是该方法下的红黑树key表示时间或者时间差
ngx_rbtree_node_t **p;

for ( ;; ) {

/*
* Timer values
* 1) are spread in small range, usually several minutes,
* 2) and overflow each 49 days, if milliseconds are stored in 32 bits.
* The comparison takes into account that overflow.
*/

/* node->key < temp->key */

p = ((ngx_rbtree_key_int_t) node->key - (ngx_rbtree_key_int_t) temp->key
< 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);
}

void
ngx_rbtree_delete(ngx_thread_volatile ngx_rbtree_t *tree,
ngx_rbtree_node_t *node)
{//红黑树的删除操作,若删除一个红色节点不破坏任何性质,但是删除一个黑色节点破坏了性质5(节点的所有子孙路径有相同的黑色节点)从而需要重新平衡树,删除操作有点复杂
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;//获取红黑树容器的哨兵节点(NIL叶节点)

if (node->left == sentinel) {//二叉查找树的删除操作都可以归结为删除一个只有只有儿子节点的情况,因为假设删除的节点node有两个儿子,
//会从node的左子树找一个最大或者右子树找一个最小的(这两种情形找到的节点假设为m)替代node的位置,然后node在原来m的位置上从而至多只有一个儿子
temp = node->right;
subst = node;

} else if (node->right == sentinel) {//这一else 和前面的if说明node只有一个儿子
temp = node->left;
subst = node;

} else {//node有两个儿子需要转换为只有一个儿子的情形
subst = ngx_rbtree_min(node->right, sentinel);//遍历到node->right的最左
//找到node右子树最小(左子树最大也可以,但是nginx实现了ngx_rbtree_min直接调用更加方便)的节点m,交换node和m的位置
if (subst->left != sentinel) {//找到那个非NIL的儿子记为temp
temp = subst->left;
} else {
temp = subst->right;
}
}
//subst是转换后只有一个儿子的节点(若node有两个儿子则subst是node右子树上最小的节点,若node至多只有一个儿子subst==node)
if (subst == *root) {//简单情形1: 待删除的节点为根节点且该根节点至多只有一个儿子,只用用根节点的儿子替代根节点并重绘新根为黑色即可,if里面只能是subst==node
*root = temp;
ngx_rbt_black(temp);

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

return;
}

red = ngx_rbt_is_red(subst);//是subst为红色则red为1,否则red为0

if (subst == subst->parent->left) {//将subst和其父节点断开连接(subst是最终要删除的节点)
subst->parent->left = temp;

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

if (subst == node) {//这是最开始要删除的节点node本身至多只有一个儿子那么subst == node

temp->parent = subst->parent;//重连subst的儿子(也是node的儿子)到subst的父节点

} else {//node有两个儿子,那么subst是node右子树最小的节点(右子树最左)

if (subst->parent == node) {
temp->parent = subst;//该语句感觉没必要,此处空语句即可

} else {
temp->parent = subst->parent;
}
//将subst替换到node的位置上
subst->left = node->left;
subst->right = node->right;
subst->parent = node->parent;
ngx_rbt_copy_color(subst, node);//拷贝数据

if (node == *root) {//简单情形2:和简单情形1不同的是,简单情形是node至多只有一个儿子,简单情形2的node有两个儿子,所以执行到了此处
*root = subst;//直接将新根置为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
node->right = NULL;
node->parent = NULL;
node->key = 0;

if (red) {//简单情形3:如果subst颜色为红色,删除一个红色节点不会破坏任何性质(注意node和subst位置交换了后删除操作相当于考察的是原来subst所在位置)
return;
}

/* a delete fixup */
//注意此后都是针对temp操作,因为是temp所在路径删除了一个黑色节点(这里是将subst黑色提到了原来node的位置故少了一个黑色节点),需要对temp重新平衡
while (temp != *root && ngx_rbt_is_black(temp)) {//简单情形4:若temp为红色或者temp是新根,则直接将temp重绘为黑色就行了不用进入循环体
//此后的情形就比前面的几种情形复杂多了,将用case来替代此后出现的情形,现在的问题是temp所在路径少了一个黑色
if (temp == temp->parent->left) {//temp为左二子(右儿子是对称情形)
w = temp->parent->right;//找到temp的兄弟w

if (ngx_rbt_is_red(w)) {//case2:(注:这里删除操作没有case1直接采用case2是为了和wikipedia保持一致),对temp父节点左旋转
ngx_rbt_black(w);//左旋后满足w->left == temp->parent, temp->parent->left == node且w为黑色,temp->parent为红色
ngx_rbt_red(temp->parent);
ngx_rbtree_left_rotate(root, sentinel, temp->parent);
w = temp->parent->right;//temp有了新的兄弟w
}

if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {//case3:temp的兄弟w为黑色,且w的两个儿子都是黑色,将w置为黑色后temp和w的黑色节点数相同了,但是temp->parent可能违反了性质4和5,从而转入temp->parent重新平衡
ngx_rbt_red(w);
temp = temp->parent;//注意这里已经包括了wikipedia上的case4,即temp->parent红色的话退出while循环直接将其重绘为黑色

} else {//w的左儿子和右儿子颜色不同
if (ngx_rbt_is_black(w->right)) {//case5:w的右儿子为黑色,w的左儿子必为红色,重绘w->right为黑色,并对w进行右旋然后进入case6
ngx_rbt_black(w->left);
ngx_rbt_red(w);
ngx_rbtree_right_rotate(root, sentinel, w);
w = temp->parent->right;//temp有新的兄弟
}
//case6:temp为黑色,w为黑色,w->right红色,对w左旋后交换temp->parent和w的颜色
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 {//和上面的情形是对称的
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);
}

static ngx_inline void
ngx_rbtree_left_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
ngx_rbtree_node_t *node)
{//左旋:交换节点和其右儿子的位置
ngx_rbtree_node_t *temp;

temp = node->right;
node->right = temp->left;

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

temp->parent = node->parent;

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

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

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

temp->left = node;
node->parent = temp;
}

static ngx_inline void
ngx_rbtree_right_rotate(ngx_rbtree_node_t **root, ngx_rbtree_node_t *sentinel,
ngx_rbtree_node_t *node)
{//右旋:交换节点和其左儿子的位置
ngx_rbtree_node_t *temp;

temp = node->left;
node->left = temp->right;

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

temp->parent = node->parent;

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

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

} else {
node->parent->left = temp;
}

temp->right = node;
node->parent = temp;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  红黑树