C++红黑树

一 红黑树概述

历史上 AVL 树流行的另一变种是红黑树(red-black tree)。对红黑树的操作能保证在最坏情况下动态几何操作的时间为 O(logN) 。之前介绍过AVL 树,该树都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。但 RB-Tree 出来之后,便很少有应用场合用到 AVL 。(AVL,平衡二叉树,不了解,可以先行了解后,再了解红黑树事半功倍)

红黑树是二叉查找树,但在每个节点上增加了一位存储位表示该节点的颜色,具体可以是 RED 或 BLACK。通过对任一条从根到叶子的路径上各节点着色方式的限制,红黑树确保了没有一条路径会比其他路径长到两倍,因而基本上是平衡的。它所追求的的是局部平衡而不是AVL树中的非常严格的平衡。

红黑树在二叉查找树的基础上还必须满足以下着色规则:

  1. 每个节点不是红色就是黑色
  2. 根节点为黑色
  3. 如果节点为红,其子节点必须为黑(一条路径上不能出现连续的两个红色节点)
  4. 任一节点至NULL(树尾端)的任何路径,所含之黑节点数必须相同

二 核心部分程序

1 左旋
在这里插入图片描述

/*左旋转*/
inline void
__rb_tree_rotate_left(__rb_tree_node_base* x, __r_node_base*& root)
{
	//X为旋转点
	__rb_tree_node_base* y = x->right;//令Y为旋转点的右子节点
	x->right = y->left;//X的右子节点指向Y的左子节点
	if (y->left != 0)
		y->left->parent = x;//如果Y有左儿子,则其左儿子的父节点设置为X
	y->parent = x->parent;//Y的父节点设为X的父节点
 
	if (x == root)
		root = y;//如果X为根节点,则将Y设为根节点
	else if (x == x->parent->left)
		x->parent->left = y;//如果X为其父节点的左儿子,则将X的父节点的左儿子设为Y
	else
		x->parent->right = y;//如果X为右儿子,则将其父节点的右儿子设为Y
	y->left = x;//Y的左儿子设为X
	x->parent = y;//X的父节点设为Y
}

2 右旋
在这里插入图片描述

/*右旋转,必须将所有父子关系转接过来*/
inline void
__rb_tree_rotate_right(__rb_tree_node_base* x, __rb_tree_node_base*& root)
{  
	//X为旋转点
	__rb_tree_node_base* y = x->left;//令Y为旋转点的左子节点
	x->left = y->right;//X的右子节点指向Y的右子节点
	if (y->right != 0)
		y->right->parent = x;//如果Y有右儿子,则其右儿子的父节点设置为X
	y->parent = x->parent;//Y的父节点设为X的父节点
 
	/*将Y完全替代X的地位,将X的所有父子关系转接过来*/
	if (x == root)
		root = y;//如果X为根节点,则将Y设为根节点
	else if (x == x->parent->right)
		x->parent->right = y;//如果X为其父节点的右儿子,则将X的父节点的右儿子设为Y
	else
		x->parent->left = y;//如果X为左儿子,则将其父节点的左儿子设为Y
	y->right = x;//Y的右儿子设为X
	x->parent = y;//X的父节点设为Y
}

3 平衡调整

/*调整红黑树,旋转以及改变颜色*/
inline void
__rb_tree_rebalance(__rb_tree_node_base* x, __rb_tree_node_base*& root)
{
	//X为新插入节点
	x->color = __rb_tree_red;//新插入节点初始为红色
	while (x != root && x->parent->color == __rb_tree_red)//父节点为红
	{
		if (x->parent == x->parent->parent->left)//X的父节点为其祖父节点的左儿子
		{
			__rb_tree_node_base* y = x->parent->parent->right;//Y指定为X的叔节点
			if (y && y->color == __rb_tree_red)//如果X的叔节点存在且为红色
			{
				/*调整颜色,需要向上迭代,直至满足着色法则第3点(不能红-红)*/
				x->parent->color = __rb_tree_black;//X的父节点着黑色
				y->color = __rb_tree_black;//X的叔节点Y着黑色
				x->parent->parent->color = __rb_tree_red;//X的祖父节点着红色
				x = x->parent->parent;//调整位置,将当前位置重置为X的祖父节点,开始上层迭代调整
			}
			else//X的叔节点不存在或者为黑色
			{
				if (x == x->parent->right)//X为右儿子,即内侧插入(左右)
				{
					x = x->parent;//设置旋转点为X的父节点,不影响后面if外X的值
					__rb_tree_rotate_left(x, root);//以X(实际为原X的父节点)为旋转点左旋转
				}
				//因为上面X的赋值在随后的左旋转已经彻底转换了父子关系,所以后面的X还是指向新插入节点位置(不是值)
				x->parent->color = __rb_tree_black;//将X的父节点着黑色
				x->parent->parent->color = __rb_tree_red;//X的祖父节点
				__rb_tree_rotate_right(x->parent->parent, root);//以X的祖父节点为旋转点右旋转
			}
		}
		else//X的父节点为其祖父节点的右儿子
		{
			__rb_tree_node_base* y = x->parent->parent->left;//Y指定为X的叔节点
			if (y && y->color == __rb_tree_red)//如果X的叔节点存在且为红色
			{
				/*调整颜色,并向上迭代判断调整*/
				x->parent->color = __rb_tree_black;
				y->color = __rb_tree_black;
				x->parent->parent->color = __rb_tree_red;
				x = x->parent->parent;//调整位置
			}
			else
			{
				if (x == x->parent->left)
				{
					x = x->parent;
					__rb_tree_rotate_right(x, root);//以X(原X的父节点)为旋转点,右旋转
				}
				x->parent->color = __rb_tree_black;
				x->parent->parent->color = __rb_tree_red;
				__rb_tree_rotate_left(x->parent->parent, root);//以X的祖父节点为旋转点进行左旋转
			}
		}
	}//end while
	root->color = __rb_tree_black;//最后将根节点着色为黑色
}

三 示意图

在这里插入图片描述

四 总结

我们知道set底层是红黑树,set是不容许重复的,multiset是容许重复的,实现步骤其实就是multiset没有去判断是否存在。
Set:

/*插入节点:节点键值不允许重复,若重复则插入无效
返回值是个pair,第一元素是个 rb_tree 迭代器,指向新增节点*/
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
pair<rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator, bool>
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::insert_unique(const Value& v)
{
	link_type y = header;//y为根节点的父节点
	link_type x = root();//x为根节点
	bool comp = true;
	while (x != 0)//从根节点开始,往下寻找适当的插入点
	{
		//二叉查找树特性插入
		y = x;
		comp = key_compare(KeyOfValue()(v), key(x));//比较新增值v 和x节点的键值
		x = comp ? left(x) : right(x);//v遇"大"则往左,遇"小或等于"往右
	}//while内单次每次运行结束,y总为x的父节点
	
	iterator j = iterator(y);//此时y节点必为叶子节点
	if (comp)//v小于比较值,将插入左侧
	{	
		if (j == begin())//begin()实际就是header->left,也就是树的最左节点
			return pair<iterator, bool>(__insert(x, y, v), true);//转入实际插入函数__insert()
		else//如果插入点之父节点不为最左节点
			--j;//调整j,--重载,调用decrement(),即找到比当前节点小的最大节点
		//与此同时还有++重载,调用increment(),找到比当前节点大的最小节点
	}
	if (key_compare(key(j.node), KeyOfValue()(v)))//遇"小"将插入右侧(v值大)
		return pair<iterator, bool>(__insert(x, y, v), true);
	/*以上,x为新插入点位置,y为插入点之父节点,v为键值*/
 
	return pair<iterator, bool>(j, false);//存在重复键值就不插入
}

Multiset

/*插入节点:节点键值允许重复
返回值是一个 rb_tree 迭代器,指向新增节点*/
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::insert_equal(const Value& v)
{
	link_type y = header;//y为根节点的父节点
	link_type x = root(); //x为根节点
	while (x != 0)//从根节点开始,往下寻找适当的插入点
	{
		y = x;
		//直接找到合适位置,无需考虑键值是否重复
		x = key_compare(KeyOfValue()(v), key(x)) ? left(x) : right(x);
	}
	return __insert(x, y, v);
}

从上面可知,真正的插入操作是__insert()

/*x为新增节点位置,y为新增节点的父节点,v为新增节点的键值*/
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::
__insert(base_ptr x_, base_ptr y_, const Value& v) {
	link_type x = (link_type)x_;
	link_type y = (link_type)y_;
	link_type z;
 
	if (y == header || x != 0 || key_compare(KeyOfValue()(v), key(y)))
	{
		z = create_node(v);//为新节点配置空间
		left(y) = z;                // also makes leftmost() = z when y == header
		if (y == header)//如果y为头节点,也就是空树第一次插入节点
		{
			root() = z;//z为根节点,与header节点互为父节点
			rightmost() = z;//树的最大节点
		}
		else if (y == leftmost())//如果y为最左节点
			leftmost() = z;//修正最小节点
	}
	else//上面三种全不满足,即插入键值大于插入点的父节点,右侧插入
	{
		z = create_node(v);
		right(y) = z;//使新节点成为插入点的父节点y的右子结点
		if (y == rightmost())
			rightmost() = z;          // maintain rightmost() pointing to max node
	}
	//设置新增节点z的关系
	parent(z) = y;
	left(z) = 0;
	right(z) = 0;
	//平衡红黑树
	__rb_tree_rebalance(z, header->parent);
 
	++node_count;//节点数累加
	return iterator(z);//返回迭代器,指向新增节点
}

引用文章链接:

https://blog.csdn.net/wenqian1991/article/details/26621657
https://blog.csdn.net/sawiii/article/details/100548870

猜你喜欢

转载自blog.csdn.net/GreedySnaker/article/details/113835587