红黑树

红黑树的性质

1.节点非红即黑。
2.根节点是黑色。
3.所有NULL结点称为叶子节点,且认为颜色为黑。
4.所有红节点的子节点都为黑色。
5.从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点。

一共五点性质,初看有点懵。待我来给你解释:

前面两点每什么好说的,从第三点开始:

3、此叶子结点非彼叶子结点,我觉得它这里把NULL认为是叶子结点,是为了更加清楚的看到每一条路径。

4、所有的红色结点的孩子结点为黑色结点;换句话说,在红黑树中不存在两个连续的红色结点

5、第五条性质我觉得是维持红黑树平衡的一条最重要的性质。由于第四条性质的限制,树中最长的那一条路径一定是红黑结点向间隔的;最短的路径就全都是黑结点。那么:最长路径 <= 2 * 最短路径。有这一点的限制,就很好的维持了红黑树的平衡了。

所有说在红黑树上搜索的最差时间复杂度为2 * O(logN),而AVL树的时间复杂度为O(logN),这一看来AVL树的效率比红黑树的效率更优,但是在实际中,使用红黑树却比AVL树的频率更高。这是因为现在硬件性能已经很好了,O(logN)这一时间复杂度已经很优了,就算是2倍还是很快。而且AVL树的平衡非常严格,需要旋转的次数比红黑树多。

红黑树结点的定义

cpp 复制代码
enum Colour
{
	BLACK,
	RED,
};
template<class K, class V>
class RBTreeNode
{
	RBTreeNode<K, v>* _left;
	RBTreeNode<K, v>* _right;
	RBTreeNode<K, v>* _parent;
	pair<K, V> _kv;
	Colour _col;
};

红黑树结点的插入

我们插入一个结点,默认该结点为红色,因为假如默认黑色,那么就会破坏第4点性质,插入了一个黑色结点,那么路径就会+1;如果默认红色,假如插入的结点的父结点是黑色,那么就不会破坏红黑树的性质。

红黑树结点的插入可分为两大步

  1. 按照二叉搜索树的规则插入结点
  2. 检测结点插入后,红黑树的性质是否被破坏

第一步在我之前的文章有记录,这里就不多复述了。

对于第二步,可分为三种情况:(令插入的红色结点为cur,p为插入结点的父结点,u为插入结点的叔叔结点,g为插入结点的祖父结点)
调整颜色主要是维护红黑树的这两个性质:没有连续的红色结点、子树中每条路径的黑色结点数量不变。

情况1:p为红,g为黑,u存在且为红

上图可以代表所有情况1

注意:这里的a,b,c,d,e,可能为空 ,这里的cur,可能是新增的结点,也可能是cur结点的子树通过变化,导致cur变成红色。但总得来说,只要符合该条件,都可以统一处理

处理方法:

如上图:p、u变红,g变黑, 但是这颗树可能只是子树,那么就要对g结点分情况讨论了:

1、当g结点为根结点,那么在调整完之后,将g改为黑色

2、当g结点是孩子结点,并且g的双亲如果是红色,那么就继续往上调整(cur = grandfather;

parent = cur->_parent;)

情况2:p为红,g为黑,u不存在 or u存在且为黑

1、当u不存在时,cur一定为新增结点。

这种情况下需要对g右单旋

2、当u存在并且为黑色时:

注意:这里的cur原本应该是黑色,因为保持路径黑色结点数量相同,u为黑色,所有cur应该是黑色。而现在cur是红色,是因为在cur的子树中,出现了情况1,进行了调整,也就是说在a,b两颗子树中,都至少存在一个黑色结点。

调整流程:无论是u存不存在,都不是简单的颜色变化,就能够维持红黑树的性质的,这时候就需要旋转处理了。
旋转方式:

  1. cur、p、g三结点形成一条左单边树,对g右单旋
  2. cur、p、g三结点形成一条右单边树,对g左单旋

旋转结束后,需要进行颜色调整:p变黑,g变红。

情况3:p为红,g为黑,u不存在 or u存在且为黑

可以发现情况三和情况二的颜色情况是一样的,其实情况三是情况二的差别不在结点的颜色上,差别在于结点的位置。情况二只是对于cur、p、g三结点位于单边树的情况进行分析,而接下来就要对三结点形成折线进行分析。

首先还是对于u是否存在进行讨论:

1、当u不存在:

和情况二是一样的,当u不存在时,cur一定为新增结点。

2、当u存在且为黑:

也是和情况二是一样的,cur也是在其子树中发生了情况一的颜色调整,才变为红色,原本应为黑色。

调整流程:其实我并没有将这个旋转流程画完,其实上面进行一次单旋后,可以发现和情况二的初始情况是一样的,只是p和cur两结点的位置变换了而已。

旋转方式:(这里不同于情况二,这三个结点形成一条折线)

  1. p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;
  2. p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
  3. 交换cur和p结点,变为情况二,一起处理。

插入整体代码实现

cpp 复制代码
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		 //新增的红结点
		cur->_col = RED;
		while (parent && parent->_col == RED)
		{
			 //红黑树的调节关键看uncle结点
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				// 情况1:uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
				//	 继续往上处理
					cur = grandfather;
					parent = cur->_parent; // 父亲可能不存在,所以在循环条件加上
				//	 有可能为根结点,但是为红,所以在最后暴力处理为黑
				}
			//	 uncle 不存在 or 为黑
				else
				{
					// 情况三:双旋 -> 变成单旋
					if (cur == parent->_right)
					{
						RotateL(parent);
						swap(parent, cur); // 变成第二种情况
					}
					// 情况二(ps:有可能是第三种情况变过来的)
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)
					{
						RotateR(parent);
						swap(cur, parent);
					}
					RotateL(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
					break;
				}
			}

		}
		_root->_col = BLACK; // 暴力处理根结点为黑色
		return true;
	}
	void RotateL(Node* parent)
	{
		// 注意:链接关系一对一对去断开和连接,这样不容易混乱,更加有条理
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		subR->_left = parent;
		 //假如parent只是一颗子树的根结点,那么需要记录一下parent的父结点
		Node* ppNode = parent->_parent;
		parent->_parent = subR;
		// 1、原来parent是这颗树的根,现在subR是根
		 //2、parent为根的树只是整颗树的子树,改变链接关系,subR顶替它的位置
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			subR->_parent = ppNode;
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
		}
	}
	 //右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		subL->_right = parent;
		Node* ppNode = parent->_parent;
		parent->_parent = subL;
		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			subL->_parent = ppNode;
			if (ppNode->_left == parent)
				ppNode->_left = subL;
			else
				ppNode->_right = subL;
		}
	}
相关推荐
Ysjt | 深1 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__2 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
Microsoft Word2 小时前
c++基础语法
开发语言·c++·算法
一只小小汤圆2 小时前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz3 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE3 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
ö Constancy4 小时前
c++ 笔记
开发语言·c++
fengbizhe4 小时前
笔试-笔记2
c++·笔记
徐霞客3204 小时前
Qt入门1——认识Qt的几个常用头文件和常用函数
开发语言·c++·笔记·qt