红黑树

红黑树的性质

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;
		}
	}
相关推荐
奋斗的小花生1 小时前
c++ 多态性
开发语言·c++
pianmian11 小时前
python数据结构基础(7)
数据结构·算法
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
UestcXiye3 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
霁月风4 小时前
设计模式——适配器模式
c++·适配器模式
ChoSeitaku4 小时前
链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)
数据结构·考研·链表
偷心编程4 小时前
双向链表专题
数据结构
香菜大丸4 小时前
链表的归并排序
数据结构·算法·链表
jrrz08284 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
咖啡里的茶i4 小时前
Vehicle友元Date多态Sedan和Truck
c++