红黑树

1.概述

红黑树是一颗二叉搜索树,它在每个节点上增加了一个存储位表示节点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,红黑树确保没有路径会比其他路径长出2倍,因而是近乎平衡的

2.红黑树性质

一个红黑树是满足下面红黑性质的二叉搜索树:

  1. 每个节点或是红色,或是黑色
  2. 根节点是黑色
  3. 每个叶子节点是黑色的(叶子是NIL节点)
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的
  5. 对每个节点,从该结点到其所有后代结点的简单路径上,均包含相同的黑色结点

每个红黑树也是一个特化的二叉查找树,因此红黑树上的只读操作与普通二叉树上的只读操作相同。然而,在红黑树上进行插入操作和删除操作会导致不再匹配红黑树的性质。恢复红黑树的性质需要少量O(logn)的颜色的变更和不超过三次树旋转。虽然插入和删除很复杂,但操作时间仍可以保持为0(logn)

3.红黑树应用

红黑树的统计性能好于平衡二叉树(AVL树),因此在内核中也有很多应用。

  • 在linux非实时调度中的应用
  • 在linux虚拟内存中的应用

AVL树和红黑树都是基于BST树(二叉搜索树),对于只读操作均和BST树相同,但BST树连续插入顺序递增或递减会导致BST树退化成链表,性能大幅度降低。

因此,AVL树红黑树均在BST树的基础上进行了进一步的优化。以增加旋转操作(增加算法复杂度)来提升对于最坏情况下的性能。

其中,红黑树为了降低原有算法复杂度,就以提升数据结构复杂度来间接减轻其算法复杂度。即在其结点中增加颜色属性,颜色只能有红色黑色。在红黑树中只要任意结点左右孩子的黑色高度平衡(两边任意路径黑色结点数量相同)即可且对于有些情况可以通过变色来代替旋转从而减少一定的空间开销

AVL树采用平衡因子的绝对值<2来保证平衡,这种平衡是高度平衡的。要保证这种平衡需要更复杂的操作来维持。
红黑树采用同一结点到叶结点的任意路径黑色结点数量相同来保证平衡,这种平衡只能保证从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。保证这种平衡只需要较少的操作来维持。

从网络大神做的实验结果来看:

  • 红黑树由于插入删除时较少的旋转操作,对于需要频繁进行插入删除的场景性能比AVL树好
  • 反之,对于不需要频繁插入删除的场景。由于AVL树的高度平衡,使得同等结点数量下,AVL树的高度比红黑树更低,使得AVL树的查找性能比红黑树好。

4.红黑树算法

为了更方便的阐述红黑树算法,使用一个哨兵来代表NIL,对于一颗红黑树T,哨兵T.nil是一个与树中普通结点有相同属性的对象,颜色为BLACK,所有指向NIL的指针都用指向哨兵T.nil的指针替换。结果如下:
在这里插入图片描述
搜索树的插入和删除在含n个关键字的红黑树上,运行花费时间为O(logn)。由于这两个操作都对树做了修改,可能违反了红黑树的性质,为了维护这些性质,必须要改变树中某些节点的颜色以及指针结构。

指针结构的修改是通过旋转来完成的。分为左旋和右旋。当在某个节点x上做左旋转时,假设它的右孩子为y而不为NULL;x可以为右孩子不是NULL结点树内任意结点。左旋以x到y的链为“支轴”进行。它使y成为该子树的根结点,x成为y的左孩子,y的左孩子成为x的右孩子。
左旋和右旋的动态变化如下:
在这里插入图片描述
在这里插入图片描述

左旋的伪代码如下:

LEFT-ROTATE(T, x)
	y = x.right
	x.right = y.left

	if y.left !=T.nil
		y.left.p = x

	y.p = x.p

	if x.p == T.nil
		T.root = y
	else if x == x.p.left
		x.p.left = y
	else
		x.p.right = y
		y.left 	  = x
		x.p       = y

在这里插入图片描述

4.1插入过程

在红黑树中,可以自O(logn)时间内完成向一棵含n个结点的红黑树中插入一个新结点,假设向树T中新插入结点为z,就像T是一棵普通二叉搜索树一样,然后将z着为红色(直接选择红色,是为了尽可能的少违反红黑树的性质)。插入以后,为了保证红黑树的性质,我们调用一个辅助程序FIXUP来对结点重新着色并旋转,插入结点z的伪程序如下:

RB-INSERT(T, z)
	y = T.nil
	x = T.root

	while x != T.nil
		y = x
		if z.key < x.key
			x = x.left
		else
			x = x.right

	z.p = y

	if y == T.nil
		T.root = z
	elseif z.key < y.key
		y.left  = z
	else
		y.right = z
	z.left  = T.nil
	z.right = T.nil
	z.color = RED

	RB-INSERT-FIXUP(T, z)

插入新结点后,可能违反红黑树的性质,所以调用RB-INSERT-FIXUP(T, z)进行调整。伪代码如下:

RB-INSERT-FIXUP(T, z)
	while z.p.color == RED
		if z.p == z.p.p.left
			y = z.p.p.right

			if y.color == RED
				z.p.color   = BLACK		//case 1
				y.color     = BLACK		//case 1
				z.p.p.color = RED        //case 1
				z           = z.p.p      //case 1
				continue
			else if z == z.p.right
				z = z.p				//case 2
				LEFT-ROTATE(T, z)		//case 2
			
			z.p.color  = BLACK			//case 3
			Z.p.p.color= RED			//case 3
			RIGHT-ROTATE(T, z.p.p)		//case 3

		else (same as then clause 
		with "riht" and "left" exchanged)

	T.root.color = BLACK

由于插入的节点是红色的,因此对于红黑树的5条性质,只会违反性质2和性质4,因此在上面伪代码中的23行,强行置根的颜色为黑,接着就是对违反的性质4进行处理,while循环就是处理该情况。具体分为下面几种情况:

前提,我们假设插入结点的父节点是其祖父的左孩子,右孩子的情况跟左孩子是对称的

  • case 1:y= z.p.p.right 叔节点是红色的

    由于z.p和y都是红色的,z.p.p是黑色的,所以将z.p和y都设置为黑色,将z.p.p着色为红色保持性质5,然后将z.p.p指针作为新的z指针来重复while循环,只到z.p变为黑色为止。

在这里插入图片描述

  • case 2,3叔节点是黑色,区别是z是z.p的右孩子还是左孩子

    在情况二中,节点z是它的父节点的右孩子,可以立即使用一个左旋来将此情形转变为情况三

    如下图所示,此时z变成左孩子,由于z和z.p都是红色,性质4会被破坏。下图中ABC的子树都有一个黑色根节点,在情况三下,只要改变某些节点的颜色并做一次右旋,如下图,由于在一行中不再存在两个红色的点,此时z.p是黑色的,所以无需再执行一次while循环。

在这里插入图片描述

关于z.p是z.p.p右孩子的情况,跟左孩子是对称的,所以不再赘述。从网上找了一张图,总结了杀入过程。

![](https://i-blog.csdnimg.cn/blog_migrate/b761b6466e0a1195bc66b8059842e8a1.jpeg)
4.2删除过程

红黑树的删除过程比插入过程要复杂一些。本文重点不再讲究原理,而是重在使用,充分的“拿来主义”。所以这里不再阐述删除的过程,只截取了下图。关于具体过程以及算法推导,建议参考《算法导论》

5.红黑树的linux实现

使用linux中的红黑树必须包含头文件#Include<linux/rbtree.h>

#define	RB_RED		0
#define	RB_BLACK		1

struct rb_node {
	unsigned long  __rb_parent_color;
	struct rb_node *rb_right;
	struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
    /* The alignment might seem pointless, but allegedly CRIS needs it */

struct rb_root {
	struct rb_node *rb_node;
};

#define RB_ROOT	(struct rb_root) { NULL, }

关于红黑树的的节点rb_node的定义如上,和内核链表实现类似,我们需要将rb_node嵌入到我们的数据结构中,内核代码doc中给出的例子如下:

  struct mytype {
  	struct rb_node node;
  	char *keystring;
  };

上述定义中rb_left,rb_right分别指向左右孩子,重点看下

__rb_parent_color

既是指向父节点的指针,也代表本节点的颜色,因为rb_node使用–attribute–强制字节对齐,所以rb_node地址最低两位始终用不上,所以可以借一位用来表示颜色。如下:

static inline void rb_set_black(struct rb_node *rb)
{
	rb->__rb_parent_color |= RB_BLACK;
}

使用红黑树之前,需要定义一个根节点如下:

struct rb_root mytree = RB_ROOT;

内核中红黑树的实现是针对速度优化的,因此我们需要实现自己的搜索和插入函数,内核文档给出了下面的例子:

  struct mytype *my_search(struct rb_root *root, char *string)
  {
  	/*根节点也是包含数据的,跟内核链表不一样*/
  	struct rb_node *node = root->rb_node;

  	while (node) {
  		/*利用红黑树节点指针,获取被嵌入母数据的地址*/
  		struct mytype *data = container_of(node, struct mytype, node);
		int result;

		result = strcmp(string, data->keystring);

		if (result < 0)
  			node = node->rb_left;
		else if (result > 0)
  			node = node->rb_right;
		else
			/*返回查找到的数据节点的地址*/
  			return data;
	}
	return NULL;
  }

插入节点,首先需要搜索插入的位置,插入后需要重新平衡(重新着色),保持红黑树的性质。

插入搜索与上一次搜索的不同之处在于要在其上移植新节点的指针的位置。新节点也需要指向其父节点的链接以进行重新平衡。

  int my_insert(struct rb_root *root, struct mytype *data)
  {
  	struct rb_node **new = &(root->rb_node), *parent = NULL;

  	/* Figure out where to put new node */
  	while (*new) {
  		struct mytype *this = container_of(*new, struct mytype, node);
  		int result = strcmp(data->keystring, this->keystring);

		parent = *new;
  		if (result < 0)
  			new = &((*new)->rb_left);
  		else if (result > 0)
  			new = &((*new)->rb_right);
  		else
  			return FALSE;
  	}

  	/* Add new node and rebalance tree. */
  	rb_link_node(&data->node, parent, new);
  	rb_insert_color(&data->node, root);

	return TRUE;
  }

下面我们来看下内核提供的插入和重新平衡的函数:rb_link_node&rb_insert_color

static inline void rb_link_node(struct rb_node * node, struct rb_node * parent, struct rb_node ** rb_link)
{	
	/*配置父节点地址,最低位默认是0,也就是红色*/
	node->__rb_parent_color = (unsigned long)parent;
	/*新节点左右孩子都为空*/
	node->rb_left = node->rb_right = NULL;
	
	/*将node节点加入红黑树中*/
	*rb_link = node;
}

rb_link_node的入参rb_link是二级指针,为什么要传入二级指针呢?

int a = 1;
int b = 2;

void swap1(int a, int b)
{
	int temp = 0;
	
	temp = a;
	a    = b;
	b    = temp;
}
printf("a:%u, b:%u\r\n",a,b);
void swap2(int *a, int *b)
{
	int temp = 0;
	
	temp = *a;
	*a   =  *b
	*b   =  temp;
}
printf("a:%u, b:%u\r\n",a,b);

在上面的代码中,swap1是无法完成a,b值交换的,c语言中函数的参数是值传递的,传入的是值的副本,所以跳出函数后,a,b值还保持原样。

在swpa2,传递是a,b变量的地址,函数直接将值存入到对应地址中,因此能成功改变a,b的值。

同理可得,如果想在函数中改变一级指针的的值,并且带出到函数之外的话,需要向函数中传入一级指针的变量的地址,这样就可以在函数内部改变一级指针的指向,并带出函数外。

void rb_insert_color(struct rb_node *node, struct rb_root *root)
{
	__rb_insert(node, root, dummy_rotate);
}
/*大写字母表示黑色,小写字母表示红色*/
static __always_inline void
__rb_insert(struct rb_node *node, struct rb_root *root,
	    void (*augment_rotate)(struct rb_node *old, struct rb_node *new))
{
	struct rb_node *parent = rb_red_parent(node), *gparent, *tmp;

	while (true) {
		/*
		 * Loop invariant: node is red
		 *
		 * If there is a black parent, we are done.
		 * Otherwise, take some corrective action as we don't
		 * want a red root or two consecutive red nodes.
		 */
		/*如果根节点为空,设置本节点的颜色为黑色,父节点为空*/
		if (!parent) {
			rb_set_parent_color(node, NULL, RB_BLACK);
			break;
		/*父节点为黑色,不用处理了,直接退出*/
		} else if (rb_is_black(parent))
			break;
		/*祖父节点*/
		gparent = rb_red_parent(parent);

		tmp = gparent->rb_right;
		/*父亲是左孩子*/
		if (parent != tmp) {	/* parent == gparent->rb_left */
			/*叔节点不为空,且为红色*/
			if (tmp && rb_is_red(tmp)) {
				/*
				 * Case 1 - color flips
				 *
				 *       G            g
				 *      / \          / \
				 *     p   u  -->   P   U
				 *    /            /
				 *   n            n
				 *
				 * However, since g's parent might be red, and
				 * 4) does not allow this, we need to recurse
				 * at g.
				 */
				/*设置父亲节点和叔节点为黑色*/
				rb_set_parent_color(tmp, gparent, RB_BLACK);
				rb_set_parent_color(parent, gparent, RB_BLACK);
				node = gparent;
				/*把祖父节点作为新节点,继续遍历*/
				parent = rb_parent(node);
				rb_set_parent_color(node, parent, RB_RED);
				continue;
			}

			tmp = parent->rb_right;
			/*新节点是父亲的右孩子*/
			if (node == tmp) {
				/*
				 * Case 2 - left rotate at parent
				 *
				 *      G             G
				 *     / \           / \
				 *    p   U  -->    n   U
				 *     \           /
				 *      n         p
				 *
				 * This still leaves us in violation of 4), the
				 * continuation into Case 3 will fix that.
				 */
				parent->rb_right = tmp = node->rb_left;
				node->rb_left = parent;
				if (tmp)
					rb_set_parent_color(tmp, parent,
							    RB_BLACK);
				rb_set_parent_color(parent, node, RB_RED);
				augment_rotate(parent, node);
				parent = node;
				tmp = node->rb_right;
			}

			/*
			 * Case 3 - right rotate at gparent
			 *
			 *        G           P
			 *       / \         / \
			 *      p   U  -->  n   g
			 *     /                 \
			 *    n                   U
			 */
			gparent->rb_left = tmp;  /* == parent->rb_right */
			parent->rb_right = gparent;
			if (tmp)
				rb_set_parent_color(tmp, gparent, RB_BLACK);
				
			__rb_rotate_set_parents(gparent, parent, root, RB_RED);
			augment_rotate(gparent, parent);
			break;
		} 
		else /*/*父亲是右孩子情况,跟左孩子的情况是对称的不再赘述*/
			
	}
}

删除时调用rb_erase函数,使用方法如下:

  void rb_erase(struct rb_node *victim, struct rb_root *tree);

Example:

  struct mytype *data = mysearch(&mytree, "walrus");

  if (data) {
  	rb_erase(&data->node, &mytree);
  	myfree(data);
  }

6.红黑树使用

每篇文章的最后,我都会写下使用的案例,以便将来具体用到的时候能够作为参考,也希望这些案例能够帮助读到文章的人。


#include <linux/kernel.h>
#include "rb_tree.h"
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include "common.h"

static struct rb_root test_root = RB_ROOT;

static inline void rb_set_black(struct rb_node *rb)
{
	rb->__rb_parent_color |= RB_BLACK;
}


static int test_rb_insert(struct test_node *node, struct rb_root *root)
{
	struct rb_node **new = &root->rb_node, *parent = NULL;
	u32 val = node->val;

	while (*new) {
		parent = *new;
		if (val < rb_entry(parent, struct test_node, rb)->val)
			new = &parent->rb_left;
		else
			new = &parent->rb_right;
	}

	rb_link_node(&node->rb, parent, new);
	rb_insert_color(&node->rb, root);

	return 0;
}

static inline void test_rb_erase(struct test_node *node, struct rb_root *root)
{
	rb_erase(&node->rb, root);
}

static ssize_t  rb_tree_debug_insert(struct device *dev, 
                               struct device_attribute *attr, 
                               const char *buf, size_t len)
{
	unsigned int  val  = 0;
	Test_Node    *node = NULL;

	sscanf(buf, "%u", &val);
	
	
	node = (Test_Node *) kmalloc(sizeof(Test_Node), GFP_ATOMIC);

	if ( NULL != node ){

		memset(node, 0, sizeof(Test_Node));
		node->val = val;
		
		test_rb_insert(node, &test_root);
		
	}else
	{
		printk("\r\nnode == NULL\r\n");
	}
	
	return len;
}

static ssize_t  rb_tree_debug_erase(struct device *dev, 
                               struct device_attribute *attr, 
                               const char *buf, size_t len)
{
	unsigned int val  = 0;
	struct rb_node *node = test_root.rb_node;
	Test_Node *data_node = NULL;

	sscanf(buf, "%u", &val);
	while (node) {
		
		if (val < rb_entry(node, struct test_node, rb)->val)
			node = node->rb_left;
		else if (val > rb_entry(node, struct test_node, rb)->val)
			node = node->rb_right;
		else
			break;
	}

	if (NULL != node)
	{
		data_node = rb_entry(node, struct test_node, rb);
	}

	if (data_node != NULL)
	{
		test_rb_erase(data_node, &test_root);
		kfree(data_node);

		printk("erase rb node val:%u\r\n", data_node->val);
	}
	

	return len;
}

void in_order_walk(struct rb_node *rb_node)
{
	Test_Node * data_node = NULL;
	
	if (NULL != rb_node)
	{
		in_order_walk(rb_node->rb_left);

		data_node = rb_entry_safe(rb_node, struct test_node, rb);
		printk("[%u]\r\n", data_node->val);
		
		in_order_walk(rb_node->rb_right);
	
	}
}

void post_order_walk(struct rb_node *rb_node)
{
	Test_Node * data_node = NULL;
	
	if (NULL != rb_node)
	{
		post_order_walk(rb_node->rb_left);
		post_order_walk(rb_node->rb_right);

		
		data_node = rb_entry_safe(rb_node, struct test_node, rb);
		printk("[%u]\r\n", data_node->val);
	
	}
}

void pre_order_walk(struct rb_node *rb_node)
{
	Test_Node * data_node = NULL;

	if (NULL != rb_node)
	{
		data_node = rb_entry_safe(rb_node, struct test_node, rb);
		printk("[%u]\r\n", data_node->val);
		
		pre_order_walk(rb_node->rb_left);
		pre_order_walk(rb_node->rb_right);
	
	}
}

static ssize_t  rb_tree_in_order_walk(struct device *dev, 
                               struct device_attribute *attr, 
                               char *buf)
{
	
	in_order_walk(test_root.rb_node);
	
	return 0;
}

static ssize_t  rb_tree_pre_order_walk(struct device *dev, 
                               struct device_attribute *attr, 
                               char *buf)
{
	
	pre_order_walk(test_root.rb_node);
	
	return 0;
}

static ssize_t  rb_tree_post_order_walk(struct device *dev, 
                               struct device_attribute *attr, 
                               char *buf)
{
	
	post_order_walk(test_root.rb_node);
	
	return 0;
}

static DEVICE_ATTR(insert,    		S_IWUSR, NULL ,rb_tree_debug_insert);
static DEVICE_ATTR(erase,   		S_IWUSR, NULL ,rb_tree_debug_erase);
static DEVICE_ATTR(in_order_walk,   S_IRUSR,  rb_tree_in_order_walk,NULL);

static DEVICE_ATTR(pre_order_walk,  S_IRUSR,  rb_tree_pre_order_walk,NULL);
static DEVICE_ATTR(post_order_walk, S_IRUSR,  rb_tree_post_order_walk,NULL);

struct attribute * rb_tree_group_info_attrs[] = 
{
	&dev_attr_insert.attr,
	&dev_attr_erase.attr,
	&dev_attr_in_order_walk.attr,
	&dev_attr_pre_order_walk.attr,
	&dev_attr_post_order_walk.attr,
	
	NULL,
};

struct attribute_group rb_tree_group = 
{
	.name = "rb_tree",
	.attrs = rb_tree_group_info_attrs,
};


执行结果如下:

 echo 20 > insert 
 echo 30 > insert 
 echo 40 > insert 

cat pre_order_walk 
[30]
[20]
[40]

cat in_order_walk 
[20]
[30]
[40]

cat post_order_walk 
[20]
[40]
[30]
echo 40  > erase

cat post_order_walk
[20]
[30]
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐