浅析红黑树

一、什么是红黑树???

红黑树首先是一棵搜索二叉树,树中的每一个结点的颜色不是黑色就是红色。它的特性如下:

1、根节点是黑色

2、每一个结点不是黑色就是红色

3、不能有连续的两个红色结点

4、从任意一个结点出发,到后代中空指针的路径上,均包含相同数量的黑色结点。

例如:

二、为什么要有红黑树???

最开始我们学习了搜索二叉树,但是最后我们发现搜索二叉树有缺陷,之后我们又引入了AVL树。AVL树是一种高度平衡的二叉搜索树,能够满足增删查都是O(logN)的时间复杂度。既然AVL树已经满足了我们的期望,那么为什么还要引入红黑树呢?

这是由于AVL树是高度平衡的二叉搜索树,维持一颗AVL树的代价相比于红黑树来说是非常大的,由于它的高度平衡,使得它几乎每次插入或者删除都需要调整树。而红黑树是一种近似平衡的二叉搜索树,它满足最长路径不超过最短路径的两倍,而且它易于维护,在插入的时候也不是每次都需要调整树。

虽然红黑树整体性能上较AVL树能差一点,但是也不是差的太多,而且红黑树胜在易于维护,所以折中下来红黑树还是用的比较多的。

三、插入

一般情况下,我么将要插入的结点都默认设置成红色的。

1、如果插入的结点是根节点,则直接插入,并且将根节点染成黑色。

2、如果要插入的位置的父亲是黑色的,那么直接插入。

3、要插入的位置的父亲是红色的,这时再插入一个红色结点就会出现两个红色结点连续的情况,所以这时候我们就要调整树来重新恢复平衡。假设要插入的结点是cur,它的父亲是parent,它父亲的兄弟是uncle,它的祖父是grand。

情况1:

因为cur的父亲是红色结点,根据红黑树的性质可知,grand一定不为空,且一定是黑色。假设在这种情况下cur的叔叔uncle是红色的。

情况2:

假设cur的叔叔结点不存在或者存在且是黑色的。

1、假设cur是parent的左子女,parent是grand的左子女,这时候只要做一次右单旋就平衡了。

2、cur是parent的右子女,parent是grand的做子女,这时对parent进行一次左旋转转换成上面右单旋的情况。

当然,假设parent是grand的右孩子,uncle是grand的左孩子,与上面的情况是镜像的,只要左右指针互换即可。

四、删除

红黑树的删除算法与二叉搜索树的删除算法类似,但是红黑树在删除之后还要再恢复树的平衡。首先我们要找到要删除的结点,如果要删除的结点有两个孩子,那么我们要在他的右子树寻找最左孩子,与他进行交换,然后将要删除的结点转换成这个最左结点。下面重点讲一下删除之后怎么恢复树的平衡,注意下面这些操作都是建立在要删除的结点的左孩子和右孩子至少有一个为空的情况下。

1、删除的结点是红色的,则直接删除,不会影响到树的平衡。

2、如果要删除的结点是黑色的,但是他的孩子是红色的,则直接删除这个结点,再把他的孩子变为黑色的。这样的话树的平衡也没有被改变。

在这种情况下如果要删除的结点是根节点要特殊处理,且只有以下两种情况:

3、要删除的结点是黑色的,他的孩子也是黑色的,这时又分为一下几种情况,假设要删除的结点是它父亲的右孩子,他的父亲是parent,它的兄弟是subL,他的孩子是cur。

情况1:如果subL是红色的,则parent一定是黑色的,而且subL的孩子也一定是黑色的。对parent进行右旋,然后再把subL染成黑色,再把parent染成红色。

情况2:subL是黑色的情况下,根据subLL与subLR又分为一下情况

1、如果subLL与subLR都是黑色的

1.1、如果parent是红色的

1.2、如果parent是黑色的

2、subLL是红色的,subLR的颜色任意

3、subLR是红色,subLL的颜色是黑色

当然,如果要删除的结点在父亲结点的左边,与上面的分析结果是镜像的,只需要调换一下左右指针就行了。

五、写一个函数IsBlance判断一颗红黑树是不是合法的红黑树

用红黑树的定义区判断能够简单一点,判断红黑树的定义是不是都满足。

//源代码

#pragma once

enum {BLACK,RED};

template<typename K,typename V>
struct RBTreeNode
{
	int _color;
	K _key;
	V _value;
	RBTreeNode<K, V> *_left;
	RBTreeNode<K, V> *_right;
	RBTreeNode<K, V> *_parent;
	RBTreeNode(K key, V value)
		:_color(RED)               //默认结点是红色
		, _key(key)
		, _value(value)
		, _left(NULL)
		, _right(NULL)
		, _parent(NULL)
	{}

};

template<typename K,typename V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	RBTree()
		:_root(NULL)
	{}

	RBTree(const RBTree<K, V>& tree)
	{
		_Copy(tree._root, _root);
	}

	RBTree<K, V>& operator=(const RBTree<K, V>& tree)
	{
		if (this != &tree)
		{
			RBTree<K, V> tmp(tree);
			swap(_root, tmp._root);
		}
		return *this;
	}

	~RBTree()
	{
		_Destory(_root);
	}

	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
				cur = cur->_left;
			else if (cur->_key < key)
				cur = cur->_right;
			else
				return true;
		}
		return false;
	}

	bool Insert(const K& key, const V& value)
	{
		if (_root == NULL)    //如果插入的结点是根节点
		{
			_root = new Node(key, value);
			_root->_color = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = NULL;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
				return false;                //要插入的结点已经存在
		}
		cur = new Node(key, value);
		if (parent->_key>key)
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		while (cur != _root&&parent->_color == RED)      //父节点是红色的需要调整
		{
			Node* grand = parent->_parent;      //找到祖父结点
			if (parent == grand->_left)
			{
				Node* uncle = grand->_right;     //找到叔叔结点
				if (uncle&&uncle->_color == RED)      //叔叔结点是红色
				{
					grand->_color = RED;
					parent->_color = BLACK;
					uncle->_color = BLACK;
					cur = grand;
					parent = cur->_parent;      //红色结点上移,需要继续判断
				}
				else            //叔叔结点不存在或为黑色结点
				{
					//先处理双旋的情况
					if (cur == parent->_right)    //如果cur是父亲的右孩子
					{
						RotateL(parent);         //先对parent进行左旋
						parent = cur;
					}

					//如果cur是parent的右孩子,则经过旋转之后现在就变成了以grand右旋的情况
					RotateR(grand);        //对祖父结点进行右旋
					parent->_color = BLACK;
					grand->_color = RED;
					break;      //这时候就已经平衡了
				}
			}
			else
			{
				Node* uncle = grand->_left;
				if (uncle&&uncle->_color == RED)     //如果叔叔存在且为红色
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grand->_color = RED;        //红色结点上移,继续向上判断
					cur = grand;
					parent = cur->_parent;
				}
				else
				{
					//如果cur是parent的左孩子,则需要先进行右旋将双旋转换成左旋的情况
					if (cur == parent->_left)
					{
						RotateR(parent);
						parent = cur;
					}

					//在对祖父进行左旋
					RotateL(grand);
					parent->_color = BLACK;
					grand->_color = RED;
					break;
				}
			}
		}
		_root->_color = BLACK;     //把根节点置成黑色
		return true;
	}

	bool Remove(const K& key)
	{
		Node* cur = _root;
		Node* parent = NULL;
		Node* del = NULL;

		while (cur)            //寻找要删除的结点
		{
			if (cur->_key > key)
				cur = cur->_left;
			else if (cur->_key < key)
				cur = cur->_right;
			else
				break;
		}

		if (cur == NULL)
			return false;           //没找到结点,删除失败
		//如果要删除的结点有两个孩子,则先转化成只有一个孩子或者没有孩子的情况
		if (cur->_left != NULL&&cur->_right != NULL)
		{
			Node* minRight = cur->_right;       //记录要删除的结点在的右子树的最左结点
			while (minRight->_left)
			{
				minRight = minRight->_left;
			}

			//采用交换删除
			cur->_key = minRight->_key;
			cur->_value = minRight->_value;

			cur = minRight;         //cur指向要删除的结点
		}
		parent = cur->_parent;       //找到要删除的结点的父亲
		del = cur;                   //del指向要删除的结点
		if (cur->_left == NULL)        //要删除的结点的左孩子为空或者不存在
		{
			if (cur == _root)           //如果要删除的结点是根节点,则删除之后就已经平衡
			{
				_root = cur->_right;
				if (cur->_right)                  //如果根节点的右孩子不为空的话,则它一定是红色
				{
					_root->_parent = NULL;
					_root->_color = BLACK;
				}
				delete del;
				return true;
			}
			//将要删除的结点的孩子链接到要删除的结点的父亲下面
			if (parent->_left == cur)             //cur是parent的左孩子,要删除的结点不是根节点,则一定有父亲
				parent->_left = cur->_right;
			else                                  //cur是parent的右孩子,要删除的结点不是父亲,则一定有父亲
				parent->_right = cur->_right;
			if (cur->_right)                        //如果要删除的不是叶子结点的话
				cur->_right->_parent = parent;
			cur = del->_right;                   //让cur指向要删除结点的孩子
		}
		else
		{
			if (cur == _root)          //要删除的结点是根节点,则根节点的左子树一定存在
			{
				_root = cur->_left;
				_root->_parent = NULL;
				_root->_color = BLACK;       //根节点的左孩子不为空的话它一定是红色
				delete del;
				return true;
			}
			if (parent->_left == cur)                     //要删除的不是根节点,则parent一定存在
				parent->_left = cur->_left;
			else
				parent->_right = cur->_left;
			cur->_left->_parent = parent;             //cur的左孩子一定存在
			cur = del->_left;                        //让cur指向要删除结点的孩子
		}

		if (del->_color == RED)           //如果要删除的结点就是红色的,则删除后已经平衡
		{
			delete del;
			return true;
		}

		if (del->_color == BLACK&&cur&&cur->_color == RED)   //如果要删除的结点是黑色的,且它的孩子是红色的,将孩子改为黑色就平衡了
		{
			cur->_color = BLACK;
			delete del;
			return true;
		}

		//要删除的结点是黑色的,且它的孩子为NULL或者是黑色的
		while (parent)
		{
			if (parent->_left == cur)            //如果要删除的结点是父亲结点的左孩子
			{
				Node* subR = parent->_right;       //subR是parent的右孩子,且一定存在
				if (subR->_color == RED)       //parent的右孩子是红色的
				{
					RotateL(parent);          //对parent进行左旋,旋转之后染色,因为cur路径上仍然少一个结点,所以继续检索cur
					parent->_color = RED;
					subR->_color = BLACK;
				}
				else                            //如果subR是黑色的
				{
					Node* subRL = subR->_left;
					Node* subRR = subR->_right;

					if (parent->_color == BLACK &&
						((subRL == NULL&&subRR == NULL) ||
						(subRL&&subRR&&subRL->_color == BLACK&&subRR->_color == BLACK)))       //如果parent以及subR和subR的孩子都为空
					{
						subR->_color = RED;     //使得subR这条路径上减少一个黑色结点,再判断向上parent
						cur = parent;
						parent = cur->_parent;
					}
					else
					{
						if (parent->_color == RED)            //如果父节点是红色
						{
							if ((subRL == NULL&&subRR == NULL) ||
								(subRL&&subRR&&subRL->_color == BLACK&&subRR->_color == BLACK))
							{
								parent->_color = BLACK;         //将父节点变为黑色
								subR->_color = RED;             //右孩子变为红,相当于在cur这条路径上曾加了一个黑色
								break;            //满足平衡,直接退出
							}
						}

						if (subRL->_color = RED)        //如果subRL为红色,先对subR进行右旋转换为左单选的情况
						{
							RotateR(subR);
							subR = subRL;                //让subR指向旋转之后新的父节点
						}

						//到这就是左单旋的情况
						RotateL(parent);           //对parent进行做单选
						if (parent->_color == RED)   //将旋转之后新的父节点subR变为与原来父节点一样的颜色
							subR->_color = RED;
						else
							subR->_color = BLACK;

						parent->_color = BLACK;       //将原来父节点染成黑色的
						subR->_right->_color = BLACK;   //因为subR的右子树上少了一颗黑色结点,所以要将红色染黑
						break;            //树已经平衡
					}
				}
			}
			else
			{
				Node* subL = parent->_left;       //要删除的结点在parent的右子树,且一定存在
				if (subL->_color == RED)       //parent的左孩子是红色的,则通过旋转让parent左右两边黑色结点个数相对
				{
					RotateR(parent);        //让paret右旋
					//让左右两边黑色结点相同,都少一个结点,再继续判断cur,
					parent->_color = RED;
					subL->_color = BLACK;
				}
				else             //如果parent的左孩子是 黑色的
				{
					Node* subLR = subL->_right;
					Node* subLL = subL->_left;
					//如果父节点与subL的孩子都是黑色的,subLL的孩子要么全为空,要么全为黑
					if (parent->_color == BLACK &&
						((subLL == NULL&&subLR == NULL) ||
						(subLL&&subLR&&subLL->_color == BLACK&&subLR->_color == BLACK)))
					{
						subL->_color = RED;     //使得subL这条路径上少一个黑色接点,再继续向上判断
						cur = parent;
						parent = cur->_parent;
					}
					else
					{
						if (parent->_color == RED)      //如果父亲结点是红色结点
						{
							if ((subLL == NULL&&subLR == NULL)||
								(subLL&&subLR&&subLL->_color == BLACK&&subLR->_color == BLACK))
							{
								parent->_color = BLACK;      //让cur这条路径上的黑色接点增加一个
								subL->_color = RED;          //subL这条路径上的黑色接点个数不变
								break;
							}
						}

						if (subLR->_color == RED)       //subL的右孩子为红,先对subL进行左旋
						{
							//将双旋变为右单旋的情况
							RotateL(subL);
							subL = subLR;        //让subL指向旋转之后的父节点
						}

						RotateR(parent);       //对parent进行右单旋
						//将旋转之后新的父节点subL变为parent的颜色
						if (parent->_color == RED)
							subL->_color = RED;
						else
							subL->_color = BLACK;

						parent->_color = BLACK;
						subL->_left->_color = BLACK;
						break;
					}
				}
			}
		}
        _root->_color = BLACK;
		delete del;
		return true;
	}

	bool IsBlance()
	{
		if (_root == NULL)           //如果是空树,则就是平衡的
			return true;

		if (_root->_color == RED)     //如果树的根节点是红色的,则这棵树就不是平衡的
			return false;

		int count = 0;
		//用count统计这棵树最左路中黑色结点的个数,作为与其他路径比较的基准
		Node* cur = _root;
		while (cur)
		{
			if (cur->_color == BLACK)
				count++;
			cur = cur->_left;
		}
		int num=0;
		return _IsBlance(_root,num,count);
	}
protected:
	bool _IsBlance(Node* root,int num,const int& count)
	{
		if (root == NULL)
		{
			return num==count;
		}

		//如果这个结点是红色的,就去判断他的父亲是什么颜色,如果这个结点是红色的,则它一定不是根节点
		if (root->_color == RED&&root->_parent->_color==RED)
			return false;
		if (root->_color == BLACK)
			num++;

		return _IsBlance(root->_left,num,count)&&_IsBlance(root->_right,num,count);
	}

	void _Copy(Node* root,Node* &newroot)
	{
		if (root == NULL)
			return;
		Node* cur = new Node(root->_key,root->_value);
		cur->_color = root->_color;

		newroot = cur;
		cur->_parent = newroot;

		_Copy(root->_left,cur->_left);
		_Copy(root->_right,cur->_right);
	}

	void _Destory(Node* root)
	{
		if (root == NULL)
			return;
		_Destory(root->_left);
		_Destory(root->_right);
		delete root;
	}

	void RotateL(Node* parent)         //左旋
	{
		Node* ppNode = parent->_parent;
		Node* subR = parent->_right;

		parent->_right = subR->_left;
		if (subR->_left)
			subR->_left->_parent = parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (ppNode == NULL)
		{
			_root = subR;
			_root->_parent = NULL;
		}
		else
		{         //与上层进行链接
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
			subR->_parent = ppNode;
		}
	}

	void RotateR(Node* parent)          //右旋
	{
		Node* ppNode = parent->_parent;
		Node* subL = parent->_left;

		parent->_left = subL->_right;
		if (subL->_right)
			subL->_right->_parent = parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (ppNode == NULL)
		{
			_root = subL;
			_root->_parent= NULL;
		}
		else                           //与上层结点链接
		{
			if (ppNode->_left == parent)
				ppNode->_left = subL;
			else
				ppNode->_right = subL;
			subL->_parent = ppNode;
		}
	}
private:
	Node* _root;
};
时间: 2024-10-27 18:49:50

浅析红黑树的相关文章

浅析红黑树算法

红黑树简介 红黑树是一种自平衡二叉查找树,也有着二叉搜索树的特性,保持着右边始终大于左边结点key的特性.前面提到过的AVL树,也是二叉搜索树的一种变形,红黑树没有达到AVL树的高度平衡,换句话说,它的高度,并没有AVL树那么高的要求,但他的应用却更加的广泛,实践中是相当高效的,他可以在O(log n)的时间内做查找.插入.删除操作.在C++ STL中,set.multiset.map.multimap等都应用到的红黑树的变体. 红黑树在平衡二叉搜索树的前提下,每个节点新增了 _color 这一

B树、B+树、红黑树、AVL树比较

B树是为了提高磁盘或外部存储设备查找效率而产生的一种多路平衡查找树. B+树为B树的变形结构,用于大多数数据库或文件系统的存储而设计. B树相对于红黑树的区别 在大规模数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘IO读写过于频繁,进而导致效率低下的情况.为什么会出现这样的情况,我们知道要获取磁盘上数据,必须先通过磁盘移动臂移动到数据所在的柱面,然后找到指定盘面,接着旋转盘面找到数据所在的磁道,最后对数据进行读写.磁盘IO代价主要花费在查找所需的柱面上,树的深度过大会造成磁盘IO频繁读

红黑树之删除节点

红黑树之删除节点 上一篇文章中讲了如何向红黑树中添加节点,也顺便创建了一棵红黑树.今天写写怎样从红黑树中删除节点. 相比于添加节点,删除节点要复杂的多.不过我们慢慢梳理,还是能够弄明白的. 回顾一下红黑树的性质 红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色.在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求: 节点是红色或黑色. 根节点是黑色. 每个叶节点(这里的叶节点是指NULL节点,在<算法导论>中这个节点叫哨兵节点,除了颜色属性外,其他属性值都为任

数据结构与算法-红黑树

前言 红黑树是工程中最常用到的一种自平衡二叉排序树,其和AVL树类似,都是在进行插入.删除时通过一定的调整操作来维持相对稳定的树高,从而获得较好的查询性能. 性质 1. 节点是红色或黑色. 2. 根节点是黑色. 3 每个叶节点(null节点)是黑色的. 4 每个红色节点的两个子节点都是黑色.(从每个叶子到根的所有路径上不能有两个连续的红色节点) 5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点. 维护红黑树形状(树高)的,主要就是4.5两条性质,性质4决定了最长的路径莫过于红黑间隔

数据结构学习笔记-排序/队/栈/链/堆/查找树/红黑树

排序: 插入排序:每次从剩余数据中选取一个最小的,插入已经排序完成的序列中 合并排序:将数据分成左右两组分别排序,然后合并,对每组数据的排序递归处理. 冒泡排序:重复交换两个相邻元素,从a[1]开始向a[0]方向冒泡,然后a[2]...当a[i]无法继续往前挤的时候说明前面的更小了,而且越往前越小(挤得越往前) 堆排序:构造最大堆,每次取走根结点(自然是最大的),再调用MAX-HEAPIFY算法(见后文的堆)恢复最大堆的性质,重复取走根结点 快速排序(对A[r]-A[n]进行排序): 1.从序列

红黑树与AVL(平衡二叉树)的区别

关于红黑树和AVL树,来自网络: 1 好处 及 用途 红黑树 并不追求"完全平衡 "--它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能. 红黑树能够以 O(log2  n)  的时间复杂度进行搜索.插入.删除操作.此外,由于它的设计,任何不平衡都会在三次旋转之内解决.当然,还有一些更好的,但实现起来更复杂的数据结构 能够做到一步旋转之内达到平衡,但红黑树能够给我们一个比较"便宜"的解决方案.红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高

数据结构-红黑树

转自:http://dongxicheng.org/structure/red-black-tree/ 1. 简介 红黑树是一种自平衡二叉查找树.它的统计性能要好于平衡二叉树(AVL树),因此,红黑树在很多地方都有应用.在C++ STL中,很多部分(目前包括set, multiset, map, multimap)应用了红黑树的变体(SGI STL中的红黑树有一些变化,这些修改提供了更好的性能,以及对set操作的支持).它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它

红黑树

弄了很久,学习过程中觉得很难,但学完了,其实感觉也就那样,就是情况多了些. 首先是插入,插入的时候其实也就3种情况,因为只有当插入的节点的父亲是红色的时候,此时红黑树的性质遭到破坏,需要旋转,再分1.叔父节点为红,此时只要改变颜色,但祖父节点颜色的改变可能会破坏红黑树的性质,所以要node = grandparent,继续向上,叔父为黑,这时需要旋转,所以得判断,自身的位置,也就是自己和父亲分别是左孩子还是右孩子,2.如果自身是右,父亲是左,的先把自己也旋转到左,再和自己是左,父亲也是左的情况一

定时器管理:nginx的红黑树和libevent的堆

libevent 发生超时后, while循环一次从堆顶del timer——直到最新调整的最小堆顶不是超时事件为止,(实际是del event),但是会稍后把这个timeout的 event放到active 任务list里, 等待处理,event标记为timeout,等处理actvie队列时再由应用层callback函数决定怎么处理标记为timeout的事件. nginx处理超时时,直接删除红黑树中( event结构体里的 )rb node成员,同时调用应用层早已通过add timer注册好的