红黑树

红黑树的性质

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

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

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

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

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

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

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

红黑树结点的定义

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为插入结点的祖父结点)
调整颜色主要是维护红黑树的这两个性质:没有连续的红色结点、子树中每条路径的黑色结点数量不变。

u存在且为红p、u变黑;g变红
u不存在 or 存在且为黑

LR(RotateL(p),交换cur和p,RotateR(g));LL(RotateR(g));

RL(RotateR(p),交换cur和p,RotateL(g));RR(RotateL(g))

全部都是g变红,p变黑

插入的结点为红,那么只会破坏性质四,不存在两个连续红结点。那么当父结点为黑时,就直接插入即可。当需要进行平衡操作时,一定是父结点为红,那么父结点一定不为根,那么就一定存在g,且g为黑色。

情况1: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结点,变为情况二,一起处理。

插入整体代码实现

	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. 中序遍历是否有序
  2. 检测其是否满足红黑树的性质

先使用循环,获取一条路径上黑色结点的数量,然后不断递归,当遇到为空结点时,代表该条路径走完了,进行黑色结点比较

	bool IsBalance()
	{
		if (_root == nullptr)
			return true;
		if (_root->_col != BLACK)
			return false;

		Node* cur = _root;
		int blackCount = 0;
		while (cur)
		{
			if (cur->_col == BLACK)
				blackCount++;
			cur = cur->_left;
		}
		int count = 0;
		return _IsBalance(_root, count, blackCount);
	}
	bool  _IsBalance(Node* root, int count, int blackCount)
	{
		if (root == nullptr)
		{
			if (count != blackCount)
			{
				return false;
			}
			return true;
		}
		if (root->_parent && root->_col == RED && root->_parent->_col == RED)
		{
			return false;
		}
		if (root->_col == BLACK)
			count++;
		return _IsBalance(root->_left, count, blackCount) && _IsBalance(root->_right, count, blackCount);
	}

红黑树的删除

删除红结点不会破坏性质,删除根结点,直接删,再更新根为黑即可。

删除结点最多存在一个结点,那么这个结点肯定是红色,直接用孩子结点顶替删除结点即可

删除结点无孩子时,最为复杂:这时需要考虑b,设n为b的孩子结点(nephew侄子)

b为红p一定为黑,p和b变色,超删除结点的方向单旋p,向上调整
b为黑,b有孩子结点

对于p和b的位置有:LL(右旋p),RR(左旋(p)) n变色b,b变色p,p变黑

LR(左旋b,右旋p),RL(右旋b,左旋p)n变p,p变黑

不需要向上调整

b为黑,b无孩子结点

当p为根结点 or p为红色结点时,b变红,p变黑,结束

当p为非根非红的普通结点时,b变红,继续向上调整

	bool Erase(const pair<K, V>& kv)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		int flag = 0; // 标记是否有删除的结点
		// 寻找需要删除的结点
		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
			{
				flag = 1;
				
				if (cur->_left == nullptr)
				{
					// 假如要删除的是根结点,只要至少有一边为空,那么可以直接删
					// 删完之后,直接拿不为空的那一边顶替根结点即可,再把颜色变为黑
					if (_root == cur)
					{
						_root = _root->_right;
						if (_root)
						{
							_root->_parent = nullptr;
							_root->_col = BLACK;
						}
						delete cur;
						return true;
					}
					break;
				}
				else if(cur->_right == nullptr)
				{
					if (_root == cur)
					{
						_root = _root->_left;
						if (_root)
						{
							_root->_parent = nullptr;
							_root->_col = BLACK;
						}
						delete cur;
						return true;
					}
					break;
				}
				
				// 替代法
				// 去右子树寻找最左结点,替代要删除的结点
				else
				{
					Node* rightMin = cur->_right;
					Node* rightMinParent = cur;
					while (rightMin->_left)
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					// 找到了要替代的结点
					// 进行替换
					cur->_kv.first = rightMin->_kv.first;
					cur->_kv.second = rightMin->_kv.second;
					cur = rightMin;
					parent = rightMinParent;
					break;
				}
				 // 先不删
			}
		}
		// 没有要删除的结点
		if (flag == 0)
			return false;
		Node* deleteNode = cur;
		Node* deleteNodeParent = parent;
		// 进行平衡
		//此时cur为需要删除的结点,具有的特殊性质
		//cur最多只有一个孩子结点
		if (cur->_left != nullptr)
			cur->_left->_col = BLACK;
		else if (cur->_right != nullptr)
			cur->_right->_col = BLACK;
		// cur没有孩子结点
		else
		{
			while (cur != _root && cur->_col != RED)
			{
					Node* brother = nullptr;
					// cur为左孩子
					if (cur == parent->_left)
						brother = parent->_right;
						// cur 为右孩子
					else
						brother = parent->_left;
					// brother为黑色结点
					if (brother->_col == BLACK)
					{
						//brother有孩子结点
						if (brother->_left != nullptr || brother->_right != nullptr)
						{
							// RR
							if (brother->_right != nullptr && brother == parent->_right)
							{
								RotateL(parent);
								brother->_right->_col = brother->_col;
								brother->_col = parent->_col;
								parent->_col = BLACK;
							}
							//RL
							else if (brother->_left != nullptr && brother == parent->_right)
							{
								brother->_left->_col = parent->_col;
								parent->_col = BLACK;
								RotateR(brother);
								RotateL(parent);
								
							}
							// LL
							else if (brother->_left != nullptr && brother == parent->_left)
							{
								brother->_right->_col = brother->_col;
								brother->_col = parent->_col;
								parent->_col = BLACK;
								RotateR(parent);
								
							}
							// LR
							else if (brother->_right != nullptr && brother == parent->_left)
							{
								brother->_right->_col = parent->_col;
								parent->_col = BLACK;
								RotateL(brother);
								RotateR(parent);
								
							}
							break;
						}
						// brother无孩子结点
						else
						{
							//当parent为根 or parent为红色结点
							if (parent == _root || parent->_col == RED)
							{
								brother->_col = RED;
								parent->_col = BLACK;
								break;
							}
							//parent为非根普通黑结点
							else {
								brother->_col = RED;
								cur = parent;
								parent = parent->_parent; // 继续向上调整
							}
						}
					}
					// brother为红色结点
					else
					{
						parent->_col = RED;
						brother->_col = BLACK;
						if (cur == parent->_left)
						{
							RotateL(parent);
							//brother = parent->_right;
						}
							
						else
						{
							RotateR(parent);
							//brother = parent->_left;
						}
					}
			}
		}
		if (deleteNode->_left == nullptr) //实际删除结点的左子树为空
	{
		if (deleteNode == deleteNodeParent->_left) //实际删除结点是其父结点的左孩子
		{
			deleteNodeParent->_left = deleteNode->_right;
			if (deleteNode->_right)
				deleteNode->_right->_parent = deleteNodeParent;
		}
		else //实际删除结点是其父结点的右孩子
		{
			deleteNodeParent->_right = deleteNode->_right;
			if (deleteNode->_right)
				deleteNode->_right->_parent = deleteNodeParent;
		}
	}
	else //实际删除结点的右子树为空
	{
		if (deleteNode == deleteNodeParent->_left) //实际删除结点是其父结点的左孩子
		{
			deleteNodeParent->_left = deleteNode->_left;
			if (deleteNode->_left)
				deleteNode->_left->_parent = deleteNodeParent;
		}
		else //实际删除结点是其父结点的右孩子
		{
			deleteNodeParent->_right = deleteNode->_left;
			if (deleteNode->_left)
				deleteNode->_left->_parent = deleteNodeParent;
		}
	}
	delete deleteNode; //实际删除结点
	return true;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值