nginx学习九 高级数据结构之红黑树ngx_rbtree_t

nginx学习九 高级数据结构之红黑树ngx_rbtree_t

   
 

1、红黑树简介

先来看下算法导论对R-B Tree的介绍:

红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。

通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平的。

红黑树,作为一棵二叉查找树,满足二叉查找树的一般性质。下面,来了解下 二叉查找树的一般性质。

二叉查找树

二叉查找树,也称有序二叉树(ordered binary tree),或已排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

任意节点的左、右子树也分别为二叉查找树。

没有键值相等的节点(no duplicate nodes)。

因为一棵由n个结点随机构造的二叉查找树的高度为lgn,所以顺理成章,二叉查找树的一般操作的执行时间为O(lgn)。但二叉查找树若退化成了一棵具有n个结点的线性链后,则这些操作最坏情况运行时间为O(n)。

红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑的查找、插入、删除的时间复杂度最坏为O(log n)。

红黑树的性质:

1) 每个结点要么是红的要么是黑的。  

2) 根结点是黑的。  

3) 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。  

4) 如果一个结点是红的,那么它的两个儿子都是黑的。  

5) 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。

红黑树的主要操作:

1)查找

2)插入

3)删除

因为插入和删除会破坏红黑树的性质,所以在插入和删除之后都需要对红黑树进行调整,使其保持红黑树的性质,在调整红黑树的时候又涉及到两个基本的操作:

4)左旋

5)右旋

以上就是红黑树全部知识的简要介绍。详细讲解红黑树的文章请百度“算法之道或结构之法”或者这个链接,讲的非常详细。

下面看看nginx的红黑树是怎么实现的。

2、nginx红黑树ngx_rbtree_t

2.1 ngx_rbtree_t和ngx_rbtree_node_t

nginx实现的红黑树,包含两个结构体:ngx_rbtree_t和ngx_rbtree_node_t。

ngx_rbtree_t:

struct ngx_rbtree_s {
    ngx_rbtree_node_t     *root;//根节点
    ngx_rbtree_node_t     *sentinel;//设置树的哨兵节点
    ngx_rbtree_insert_pt   insert;//插入方法的函数指针
};

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;//节点颜色,0表示黑色,1表示红色
	u_char				 data;//数据
};

可见nginx红黑树节点基本没有存储用户的数据,因为ngx_rbtree_node_t里面只有一个u_char的data,显然这个data是不能满足用户存储数据的要求的。因此,当用户使用ngx_rbtree_t时必须自己定义数据结构并申请数据空间,例如向下面这样使用:

typedef struct
{
	ngx_rbtree_node_t node;
	ngx_int_t 		  num;
	void             *data; //存储数据的指针
}TestRBTNode;//自定义的数据结构体必须包含ngx_rbtree_node_t

详细的使用请看最后的测试列子。这里不多说。

2.2 常用的宏定义

下面是ngingx为实现红黑树提供的常用的宏定义操作:

//初始化红黑树
#define ngx_rbtree_init(tree, s, i)										   \
	ngx_rbtree_sentinel_init(s);											  \
	(tree)->root = s;														 \
	(tree)->sentinel = s;													 \
	(tree)->insert = i

//设置节点颜色为红色
#define ngx_rbt_red(node)			   ((node)->color = 1)
//设置节点颜色为黑色
#define ngx_rbt_black(node)			 ((node)->color = 0)
//若节点颜色为红色返回非0,否则返回0
#define ngx_rbt_is_red(node)			((node)->color)
//若节点颜色为黑色返回非0,否则返回0
#define ngx_rbt_is_black(node)		  (!ngx_rbt_is_red(node))
//把节点2的颜色赋值给节点1
#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)

2.3 ngx_rbtree_insert

void ngx_rbtree_insert(ngx_thread_volatile ngx_rbtree_t *tree, ngx_rbtree_node_t *node);

向红黑树中插入节点。上面说过,向红黑树中插入节点,有可能破坏红黑树的性质,这时就需要调整红黑树,哪些情况会破坏红黑树的性质呢?下面三种情况会破坏红黑树的性质:

1)如果当前结点的父结点是红色且祖父结点的另一个子结点(叔叔结点)是红色

2)当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子

3)当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子

下面是这三种情况的对应调整方法(这里只是父节点为祖父节点的左孩子(右孩子情况类同)):

如果当前结点的父结点是红色且祖父结点的另一个子结点(叔叔结点)是红色 ,调整方法如下:

1)将当前节点的父节点和叔叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点,从新的当前节点重新开始算法

如果当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子, 调整方法如下:

2)当前节点的父节点做为新的当前节点,以新当前节点为支点左旋

如果当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子 ,调整方法如下:

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 */  /*插入调整*/

	while (node != *root && ngx_rbt_is_red(node->parent)) {//当前节点不是根节点,并且节点的父节点颜色为红色
		//父节点为祖父节点的左孩子
		if (node->parent == node->parent->parent->left) {
			temp = node->parent->parent->right;//取叔叔节点
			//情况1:父节点为红,叔叔节点为红
			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 {//情况2:父节点为红,叔叔节点为黑色,当前节点为父节点的右孩子
				if (node == node->parent->right) {//解决方法:
					node = node->parent;//修改当前节点为父节点
					ngx_rbtree_left_rotate(root, sentinel, node);//左旋当前节点
				}
				//情况3:父节点为红,叔叔节点为黑色,当前节点为父节点的左孩子
				ngx_rbt_black(node->parent);//解决方法:父节点修改为黑色
				ngx_rbt_red(node->parent->parent);//祖父节点修改为红色
				ngx_rbtree_right_rotate(root, sentinel, node->parent->parent);//右旋祖父节点
			}

		} else {//父节点为祖父节点的右孩子,这也有三种情况,和上述情况操作类似,只不过旋转方向相反
			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);//根节点永远是黑色
}

插入的情况要比删除的情况简单的多!

2.4 ngx_rbtree_delete

void ngx_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)。

在红黑树中删除节点,删除节点也有可能破坏红黑树的性质,需要调整红黑树。下面四种删除操作会破坏红黑树的性质:

1)当前节点是黑+黑且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑)

2)当前节点是黑加黑且兄弟是黑色且兄弟节点的两个子节点全为黑色

3)当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色

4)当前节点颜色是黑-黑色,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意

下面是这四种情况对应的调整方法:

如果 当前节点是黑+黑且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑),调整如下:

1)把父节点染成红色,把兄弟结点染成黑色,左旋父节点

如果 当前节点是黑加黑且兄弟是黑色且兄弟节点的两个子节点全为黑色 ,调整如下:

2)把当前节点和兄弟节点中抽取一重黑色追加到父节点上,把父节点当成新的当前节点

如果 当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色,调整如下:

3)把兄弟结点染红,兄弟左子节点染黑,之后再在兄弟节点为支点解右旋

如果 当前节点颜色是黑-黑色,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意 ,调整如下:

4)把兄弟节点染成当前节点父节点的颜色,把当前节点父节点染成黑色,兄弟节点右子染成黑色,之后以当前节点的父节点为支点进行左旋。

源代码:

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;//哨兵节点

	if (node->left == sentinel) {//左子节点为空
		temp = node->right;
		subst = node;//subst记录后继节点,用着个节点代替删除节点

	} else if (node->right == sentinel) {//右子节点为空
		temp = node->left;
		subst = node;

	} else {//左右子节点都不为空
		subst = ngx_rbtree_min(node->right, sentinel);//右子树中最小的节点

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

	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 = ngx_rbt_is_red(subst);//如果后继是红色,就不需要调整红黑树
								//因为实际上删除的是后继节点
	/*下面是一些指针的修改,不详细介绍,参看二叉排序树的删除*/
	if (subst == subst->parent->left) {
		subst->parent->left = temp;

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

	if (subst == node) {

		temp->parent = subst->parent;

	} else {

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

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

		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 *///调整红黑树的四种情况,看注释

	while (temp != *root && ngx_rbt_is_black(temp)) {
		//当前节点为左节点的情况
		if (temp == temp->parent->left) {
			w = temp->parent->right;
			//情况1:当前节点为黑色,兄弟节点为红色
			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;
			}
			//情况2:当前节点为黑色,兄弟节点为黑色,兄弟节点的俩子节点也为黑色
			if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
				ngx_rbt_red(w);//解决:兄弟节点修改为红色
				temp = temp->parent;//当前节点指向父节点

			} else {//情况3:当前节点为黑色,兄弟节点为黑色,兄弟节点右子节点为黑色,左子节点为红色
				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;
				}
				//情况4:当前节点为黑色,兄弟节点为黑色,兄弟节点右子节点为红色,左子节点任意色
				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);
}

2.5 左旋和右旋,查找节点

左旋和右旋操作非常简单,这里就不介绍了,查找操作nginx没有实现,我们可以根据二叉排序树来实现一个查找函数,这里也不介绍。

http://blog.csdn.net/xiaoliangsky/article/details/39803589

http://www.cnblogs.com/yxwkf/p/4710589.html

http://www.mincoder.com/article/2990.shtml

2.6  lookup操作

lookup操作:

在接口ngx_int_t ngx_resolve_addr(ngx_resolver_ctx_t *ctx)中,调用ngx_resolver_lookup_addr完成lookup操作。

ngx_resolver_lookup_addr的源代码如下:

static ngx_resolver_node_t *

ngx_resolver_lookup_addr(ngx_resolver_t *r, in_addr_t addr)
{
    ngx_rbtree_node_t  *node, *sentinel;

    node = r->addr_rbtree.root;
    sentinel = r->addr_rbtree.sentinel;

    while (node != sentinel) {

        if (addr < node->key) {
            node = node->left;
            continue;
        }

        if (addr > node->key) {
            node = node->right;
            continue;
        }

        /* addr == node->key */

        return (ngx_resolver_node_t *) node;
    }

    /* not found */

    return NULL;
}

3、测试红黑树的例子

测试代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <ngx_core.h>
#include <ngx_config.h>
#include <ngx_conf_file.h>
#include <ngx_string.h>
#include <ngx_palloc.h>
#include <ngx_rbtree.h>

#define ngx_rbtee_data(p, type, member) (type*)((u_char*)p - offset(type, member))

//节点数据结构
typedef struct
{
	ngx_rbtree_node_t  node;//方便类型转化
	ngx_str_t		  str;
}ngx_string_node_t;

//自定义插入函数
void ngx_string_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
	ngx_rbtree_node_t *sentinel);

//先序遍历红黑树
void trave_rbtree(ngx_rbtree_node_t *root, ngx_rbtree_node_t *sentinel);

int main()
{
	ngx_rbtree_t	  tree;
	ngx_rbtree_node_t sentinel;

	ngx_rbtree_init(&tree, &sentinel, ngx_string_rbtree_insert_value);

	ngx_string_node_t strNode[10];

	ngx_str_set(&strNode[0].str, "abc0");
	strNode[0].node.key = 1;

	ngx_str_set(&strNode[1].str, "abc1");
	strNode[1].node.key = 6;

	ngx_str_set(&strNode[2].str, "abc2");
	strNode[2].node.key = 8;

	ngx_str_set(&strNode[3].str, "abc35");
	strNode[3].node.key = 11;

	ngx_str_set(&strNode[4].str, "abd4");
	strNode[4].node.key = 8;

	ngx_str_set(&strNode[5].str, "abc5");
	strNode[5].node.key = 1;

	ngx_str_set(&strNode[6].str, "abc11");
	strNode[6].node.key = 11;

	ngx_str_set(&strNode[7].str, "a6");
	strNode[7].node.key = 1;

	ngx_str_set(&strNode[8].str, "a8");
	strNode[8].node.key = 6;

	ngx_str_set(&strNode[9].str, "abc0");
	strNode[9].node.key = 6;

	ngx_int_t i;
	for (i = 0; i < 10; ++i)
	{
		ngx_rbtree_insert(&tree, &strNode[i].node);
	}

	travel_rbtree(tree.root, tree.sentinel);

	return 0;
}

void
ngx_string_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,
	ngx_rbtree_node_t *sentinel)
{
	ngx_rbtree_node_t  **p;
	ngx_string_node_t	  *strNodeX;
	ngx_string_node_t	  *strNodeY;

	for ( ;; )
	{
		if (node->key != temp->key)
		{
			 p = (node->key < temp->key) ? &temp->left : &temp->right;
		}
		else
		{
			strNodeX = (ngx_string_node_t*)node;//类型转化,因为node的地址和ngx_string_node_t的地址相同
			//strNodeY = (ngx_string_node_t*)temp;
			//或者这样用
			strNodeY = ngx_rbtree_data(temp, ngx_string_node_t, node);

			if (strNodeX->str.len != strNodeY->str.len)
			{
				p = (strNodeX->str.len < strNodeY->str.len) ? &temp->left : &temp->right;
			}
			else
			{
				p = (ngx_memcmp(strNodeX->str.data, strNodeY->str.data, strNodeX->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);//颜色设置为红色
}

void 
travel_rbtree(ngx_rbtree_node_t* root, ngx_rbtree_node_t *sentinel)
{
	if (root->left != sentinel) 
	{
		travel_rbtree(root->left, sentinel);
	}

	ngx_string_node_t* strNode = (ngx_string_node_t*)root;
	printf("key = %d , str=%s\n", root->key, strNode->str.data);

	if (root->right != sentinel)
	{
		travel_rbtree(root->right, sentinel);
	}
}

makefile:

gcc -c -g -Wall -Wextra -I. -I /home/wyp/桌面/testkNx/nginx-1.0.15/src/core \
-I /home/wyp/桌面/testkNx/nginx-1.0.15/src/event \
-I /home/wyp/桌面/testkNx/nginx-1.0.15/src/event/modules \
-I /home/wyp/桌面/testkNx/nginx-1.0.15/src/os/unix \
-I /home/wyp/桌面/testkNx/nginx-1.0.15/objs  \
/home/wyp/桌面/testkNx/nginx-1.0.15/objs/src/core/ngx_rbtree.o ngx_rbtree.c

     

    原文作者:算法小白
    原文地址: https://blog.csdn.net/hebbely/article/details/51885833
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞