红黑树 red_black_tree

红黑树

1 概念了解

**用途:**Linux 中的内存管理、进程调度、定时器。

红黑树本质上是一颗二叉树。

**二叉树:**每个节点的儿子都不能多于两个。

二叉树的一个重要应用是它们在查找中的使用,而节点键值没有任何关系的二叉树,查找时间复杂度在最坏情况下是O(n),所以就有了二叉查找树

**二叉查找树:**使二叉树成为二叉查找树的性质是,对于树中的每个节点 X,它的左子树中的所有关键字值小于 X 的关键字值,右子树中的所有关键字值大于 X 的关键字值。

但二叉查找树容易退化成一个线性序列(当是一个有序序列时),增加时间复杂度O(logn),所以给二叉查找树加入一些规则,使得每个节点的左右子树深度基本维持平衡,优化查找效率,也就是平衡二叉查找树

二叉查找树:

在这里插入图片描述

**平衡二叉查找树(AVL树):**每个节点的左子树和右子树的高度最多差 1 的二叉查找树。

但平衡二叉查找树,为了维持平衡,旋转操作较多,造成开销较大,所以,我们通过牺牲严格的平衡来换取插入操作的简洁,也就是为每个节点赋予红黑两种颜色,而不考虑节点左右子树的深度差**(红黑树)**。

平衡二叉查找树:

在这里插入图片描述

其实二叉查找树和红黑树,也就是降低树的深度,优化查找效率。

其实按照红黑树的规则,最长路径不会大于最短路径的两倍(后面说)。

1.1 红黑树约束条件

**红黑树是具有下列着色性质的二叉查找树:**但又不像平衡二叉树有那么严格的平衡条件。

  1. 每一个节点或者是红色,或者是黑色。
  2. 根是黑色的(觉得是为了方便插入)。
  3. 如果一个节点是红色,那么它的子节点必须是黑色的(不能有两个相连的红节点)。
  4. 从一个节点到一个 NULL 指针的每一条路径必须包含相同数目的黑色节点。

红黑树:

在这里插入图片描述

红黑树最长路径就是红黑相间,最短路径就是纯黑(也叫黑高),所以最长不大于最短两倍。确保了红黑树的时间复杂度。

2 自底向上插入

(自底向上插入是插入节点的方法,后面的自顶向下构造红黑树,是构造一颗红黑树。)

  1. **新插入节点为黑色:**违反条件 4,因为将会建立一条更长的黑节点路径,因此,这一项必须涂成红色,才有可能不破坏平衡。

  2. 新插入节点为红色:

    2.1 **父节点为黑色:**插入完成。

    2.1 **父节点为红色:**得到连续的红色节点,违反条件 3。在这种情况下,我们必须调整该树以确保条件 3 满足(且不引起条件 4 被破坏)。用于完成这项任务的基本操作是颜色的改变和树的旋转。

2.1 父节点为红

因为涉及到旋转操作,不能旋转之后,影响了其它子树,这里涉及到父节点的兄弟节点,我们需要考虑。

先不管为什么要考虑父节点兄弟节点,旋转之后就明白了。

2.1.1 父节点兄弟为黑

令 X 是新加的树叶,P 是它的父节点,S 是该父节点的兄弟(若存在),G 是祖父节点。

在这种情况下,只有 X 和 P 是红的,G 是黑色的,否则就会在插入之前有两个相连的红节点,违反了红黑树的法则。

此时,X 和 P 和 G 会形成一个一字链或之字链。

这里我们画出的是一般的情形,也就是使得 X 在树的中间,其实我们后面构造红黑树的时候,就是这种 X 在中间的情形。

如下所示,是一个最小失衡子树,我们的旋转操作就是在这个最小失衡子树中进行的,所以我们这里给 X 画个子树并不影响。

LL 型:

在这里插入图片描述

对应 P 和 G 之间的单旋转,我们管顺时针旋转叫做左旋。旋转按照 AVL 树更好理解。

RR 型:右旋。

在这里插入图片描述

LR 型:

在这里插入图片描述

对应双旋转,双旋转应该首先在 X 和 P 之间进行,然后在 X 和 G 之间进行。

RL 型:

在这里插入图片描述

在上述的情况下,子树的新根均被涂成黑色,因此,即使原来的曾祖节点是红的,我们也排除了两个相邻红节点的可能性。同样重要的是,这些旋转的结果使通向A、B、C诸路径的黑色节点个数保持不变。

到现在为止一切顺利。但是如果 S 是红色的?

2.1.2 父节点兄弟为红

以 LL 型为例:

在这种情况下,初始时从子树的根到C的路径上只有一个黑节点,在旋转之后,一定也只有一个黑节点。
但是在这两种情况下,在通向C的路径上,都有3个节点。由于只能有一个是黑的,又由于我们不能有连续的红节点,于是我们必须把S和子树的新根都涂成红色,而把G以及第四个节点涂成黑色,这样对当前子树来说是平衡的。

此时子树新根为红色,可是如果曾祖也是红色怎么办?

此时我们可以将这个过程朝着根的方向上滤,直到不再有两个相连的红节点,或者到达根节点为止,将根重新涂成黑色。

在这里插入图片描述

换个思路想,我们上滤的操作是为了解决新插入节点父节点的兄弟为红色的问题。

而只有P节点是红色的时候会发生旋转,只有S节点是红色的时候才需要去上滤,也就是说,只有当一个节点的两个儿子都是红色的时候,需要上滤操作。

那么我们就可以使用一种自顶向下的方法,去确保 S 不会是红色的。

3 自顶向下红黑树

**上滤操作优化:**上滤操作为了解决新插入节点父节点的兄弟为红色问题。

只有P节点是红色的时候才需要旋转,且S节点是红色的时候需要上滤。所以,只有当一个节点的两个儿子都是红色的时候,需要上滤操作。

这个过程在概念上是容易的,在向下的过程中当我们看到一个节点X有两个红儿子的时候,我们让X成为红的,而让它的两个儿子成为黑的。

在这里插入图片描述

只有当翻转后X的父节点P也是红的时候,这种翻转将打破红黑树法则。但是此时呢,我们可以通过上面旋转操作,将树重新平衡。

如果X的父节点的兄弟是红的,会如何,这种可能已经被从顶向下的过程中所排除,因此 X 的父节点的兄弟不可能是红色的!

3.1 插入节点例子

**插入节点 45:**沿树向下,节点 50 有两个红儿子,执行颜色翻转。

在这里插入图片描述

**颜色翻转:**颜色翻转之后,节点 50 的父节点也是红色,违反红黑树性质,需进行旋转。

在这里插入图片描述

**旋转:**LL型。

在这里插入图片描述

**插入:**重新平衡之后,继续沿树向下,到达 NIL 节点,插入 45。若 40 是红的,则再进行一次旋转。

在这里插入图片描述

如图所示,所得到的红黑树常常平衡的很好。经验指出,平均红黑树大约和平均 AVL 树一样深,从而查找时间一般接近最优。

红黑树的优点是执行插入所需要的开销相对较低,再有就是实践中所发生的旋转相对较少。

4 红黑树查找

**红黑树查找:**沿树向下即可。

4.1 时间复杂度

O(logN).

**定理:**结点 X 的子树至少包含 2 b h ( x ) − 1 2bh(x)-1 2bh(x)1 个内结点。( b h ( x ) bh(x) bh(x), 指 X 节点的黑高。)

节点总数: N ≥ 2 b h ( x ) − 1 N ≥ 2bh(x)-1 N2bh(x)1 --> N + 1 ≥ 2 b h ( x ) N + 1 ≥ 2bh(x) N+12bh(x) --> l o g ( N + 1 ) ≥ b h ( x ) log(N +1) ≥ bh(x) log(N+1)bh(x).

根据红黑树性质 3: b h ( x ) ≥ h ( x ) / 2 bh(x) ≥ h(x)/2 bh(x)h(x)/2.

l o g ( N + 1 ) ≥ b h ( x ) ≥ h ( x ) / 2 log(N+1) ≥ bh(x) ≥ h(x)/2 log(N+1)bh(x)h(x)/2 --> 2 l o g ( N + 1 ) ≥ h ( x ) 2log(N+1) ≥ h(x) 2log(N+1)h(x).

所以,红黑树的高度最多是 2 l o g ( N + 1 ) 2log(N+1) 2log(N+1),则时间复杂度 O ( l o g N ) O(logN) O(logN).

5 代码实现

在这里插入图片描述

red_black_tree.h

#ifndef _RED_BLACK_TREE_H_
#define _RED_BLACK_TREE_H_

#define INFINITY 0x7FFFFFFF
#define NEG_INFINITY 0x80000000

typedef enum color {
	RED,
	BLACK
}color_t;

typedef int element_t;
typedef struct red_black_node {
	element_t element;
	struct red_black_node *left;
	struct red_black_node *right;
	color_t color;
}red_black_node_t;

red_black_node_t* red_black_tree_init(void);

red_black_node_t* red_black_node_insert(element_t item, red_black_node_t *rbt);

void print_tree(red_black_node_t *rbt);

red_black_node_t * find(element_t item, red_black_node_t *rbt);

#endif /* _RED_BLACK_TREE_H_ */

red_black_tree.c

#include <stdio.h>
#include <stdlib.h>
#include "red_black_tree.h"

static red_black_node_t *null_node = NULL; /* needs init */

static red_black_node_t *current_node, *parent, *grand, *great;

red_black_node_t* red_black_tree_init(void)
{
	red_black_node_t *rbt = NULL;
	
	if (NULL == null_node) {
		null_node = (red_black_node_t *)malloc(sizeof(red_black_node_t));
		if (NULL == null_node) {
			printf("#ERROR:out of space:null_node\r\n");
			return null_node;
		}
		
		null_node->left = null_node->right = null_node;
		null_node->color = BLACK;
		null_node->element = INFINITY;
	}
	
	/* create the header node */
	rbt = (red_black_node_t *)malloc(sizeof(red_black_node_t));
	if (NULL == rbt) {
		printf("#ERROR:out of space:rbt\r\n");
		return null_node;
	}
	
	rbt->left = rbt->right = null_node;
	rbt->color = BLACK;
	rbt->element = NEG_INFINITY;
	
	return rbt;
}

static red_black_node_t* single_rotate_with_left(red_black_node_t *root_subtree)
{
	red_black_node_t *child_subtree = root_subtree->left;
	root_subtree->left = child_subtree->right;
	child_subtree->right = root_subtree;
	
	return child_subtree;
}

static red_black_node_t* single_rotate_with_right(red_black_node_t *root_subtree)
{
	red_black_node_t *child_subtree = root_subtree->right;
	root_subtree->right = child_subtree->left;
	child_subtree->left = root_subtree;
	
	return child_subtree;
}

/**/
static red_black_node_t* rotate(element_t item, red_black_node_t *parent_of_subtree)
{
	if (item < parent_of_subtree->element)
		return parent_of_subtree->left = (item < parent_of_subtree->left->element) ?
			single_rotate_with_left(parent_of_subtree->left) : 
			single_rotate_with_right(parent_of_subtree->left);
	else
		return parent_of_subtree->right = (item < parent_of_subtree->right->element) ?
			single_rotate_with_left(parent_of_subtree->right) : 
			single_rotate_with_right(parent_of_subtree->right);
}

static void handle_reorient(element_t item, red_black_node_t *rbt)
{
	current_node->color = RED;    /* do the color flip*/
	current_node->left->color = BLACK;
	current_node->right->color = BLACK;
	
	if (RED == parent->color) {    /* have a rotate*/
		grand->color = RED;
		
		if ((item < grand->element) != (item < parent->element))
			parent = rotate(item, grand);    /* start double rotation */
		current_node = rotate(item, great);
		
		current_node->color = BLACK;
	}
	rbt->right->color = BLACK;    /* make root black */
}

red_black_node_t* red_black_node_insert(element_t item, red_black_node_t *rbt)
{
	current_node = parent = grand = rbt;
	null_node->element = item;
	
	while (item != current_node->element) {    /* descend down the tree */
		great = grand;
		grand = parent;
		parent = current_node;
		
		if (item < current_node->element)
			current_node = current_node->left;
		else
			current_node = current_node->right;
		
		if (RED == current_node->left->color && RED == current_node->right->color)
			handle_reorient(item, rbt);
	}
	if (null_node != current_node) { 
		return null_node;    /*duplicate*/
	}
	
	current_node = (red_black_node_t *)malloc(sizeof(red_black_node_t));
	if (NULL == current_node) {
		printf("#ERROR:out of space:current_node\r\n");
		return rbt;
	}
	
	current_node->element = item;
	current_node->left = current_node->right = null_node;
	
	if (item < parent->element)    /* attach to its parent */
		parent->left = current_node;
	else
		parent->right = current_node;
	
	handle_reorient(item, rbt);    /* color red, maybe rotate */
	
	return rbt;
}

static void do_print(red_black_node_t *rbt)
{
	if (null_node != rbt) {
		do_print(rbt->left);
		printf("%4d-%d", rbt->element, rbt->color);
		do_print(rbt->right);
	}
}

void print_tree(red_black_node_t *rbt)
{
	do_print(rbt->right);
}

static red_black_node_t * find_item(element_t item, red_black_node_t *rbt)
{
	if (null_node == rbt)
		return rbt;
	
	if (item < rbt->element)
		return find_item(item, rbt->left);
	else if (item > rbt->element)
		return find_item(item, rbt->right);
	else 
		return rbt;
}

red_black_node_t * find(element_t item, red_black_node_t *rbt)
{
	if (NULL == rbt)
		return rbt;
	
	return find_item(item, rbt->right);
}

rbt_test.c

#include <stdio.h>
#include "red_black_tree.h"

int main (void)
{
	red_black_node_t *rbt = red_black_tree_init();
	
	int rbt_item[] = {10, 85, 15, 70, 20, 60, 30, 50, 65, 80, 90, 40, 5, 55};
	for (int item = 0; item < (sizeof(rbt_item) / sizeof(int)); ++item) {
		red_black_node_insert(rbt_item[item], rbt);
		print_tree(rbt);
		printf("\r\n");
	}
	
	printf("insert 45:\r\n");
	red_black_node_insert(45, rbt);
	print_tree(rbt);
	printf("\r\n");
	
	printf("find:");
	red_black_node_t* find_node = find(90, rbt);
	printf("%4d-%d\r\n", find_node->element, find_node->color);
	
	return 0;
}

执行结果:
在这里插入图片描述

6 红黑树删除

写的比较仓促。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值