[数据结构] 红黑树的详解

Posted 哦哦呵呵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[数据结构] 红黑树的详解相关的知识,希望对你有一定的参考价值。

目录

1. 红黑树概念

  红黑树,本质是一颗二叉搜索树 + 节点颜色限制(红/黑) + 规则约定(最长路径中的节点个数不超过最短路径中节点个数的2倍)
  在二叉搜索树中,每个节点上增加一个存储位表示节点的颜色,可能是红色或黑色。
  对于任何一条根到叶子节点路径上各个节点的着色方式的限制,红黑树确保没有没有一条比其它路径长出两倍,因而接近平衡二叉树,或者说近似平衡。

2. 红黑树性质

  • 每个节点不是黑色就是红色
  • 根节点是黑色
  • 如果有一个节点是红色的,则它的两个孩子节点必定是黑色,不可能出现连在一起的红色节点
  • 对于每个节点,从该节点到其它所有后代叶节点的路径上,均包含相同数据的黑色节点,每条路径中的黑色节点数量相同
  • 每个空指针域都是黑色

为什么满足上述性质,就能保证红黑树最长路径长度不超过最短路径的2倍?

  最重要的是要保证每条路径中黑色节点的个数相同,并且红色节点不能连续出现,所以就保证了最长路径不超过最短路径的二倍。

3. 红黑树的调整算法

  新插入节点默认位红色,因为在向红黑树中插入新节点时,如果其双亲结点的颜色是黑色的,则不会违反红黑树的性质,如果其双亲是红色的,插入红色结点后,就会违反性质四,需要对红黑树进行调整。如果新插入结点颜色位黑色,那么每次插入新节点都会违反性质四。

3.1 红黑树插入时需要调整的情况

  根据上述我们知道,插入默认节点的颜色时红色,当其父节点为黑色时,树不需要进行调整,但当其父节点为红色时需要调整。

当前结点:cur,父节点:p,祖父结点:g,叔叔结点:u

1. 当前结点为红色,父节点为红色,祖父结点为黑色,叔叔结点存在且为红色
  此种情况较简单,只需要修改父节点为黑色,叔叔节点为黑色,祖父结点为红色即可。

注意: 如果祖父为树中的一颗子树,则需要注意祖父结点的父节点的状态,如果祖父结点的父节点为红色,那么违反了性质三,还需要向上调整。

2. 当前结点为红色,父节点为红色,祖父结点为黑色,叔叔结点不存在/为黑,cur是p的左孩子

  • 如果叔叔结点不存在,则cur一定是新插入结点,如果cur不是新插入结点,则cur和p一定有一个结点的颜色时黑色,则cur和p一定有一个颜色是黑色,已经不满足性质了,所以不会到这一步。
  • 如果叔叔几结点存在,则其一定为黑色,并且cur原来的结点一定是黑色的,cur变红的原因是cur的子树在调整的过程中影响了该结点。

这里我们只看一种情况,因为上述两种情况差不了多少。

p是g的左孩子,cur是p的左孩子: 先右单旋,修改p为黑色,g为红色

3. 当前结点为红,父节点为红,祖父结点为黑,叔叔结点不存在/为黑,cur是p的右孩子

先针对p结点进行左旋,就会变成情况二的样子,之后按照情况二的处理方式处理即可。


注意:还有以上三种情况的逆情况,即树型与上述相仿,就不再赘述,只需要将操作反过来即可,下方代码中会有实现。

4. 模拟一下

序列:1 2 9 4 6 5


5. 代码实现

// 请模拟实现红黑树的插入--注意:为了后序封装map和set,本文在实现时给红黑树多增加了一个头结点

enum Color  RED, BLACK ;

template<class T>
struct RBTreeNode

	RBTreeNode<T>* _pLeft;
	RBTreeNode<T>* _pRight;
	RBTreeNode<T>* _pParent;
	T _data;
	Color _color;

	RBTreeNode(const T& val = T(), Color c = RED)
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(val)
		, _color(c)
	
;

template<class T>
class RBTree

	typedef RBTreeNode<T> Node;

public:
	RBTree()
	
		_pHead = new Node;
		_pHead->_pLeft = _pHead;
		_pHead->_pRight = _pHead;
	

	// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
	// 注意:为了简单起见,本次实现红黑树不存储重复性元素
	bool Insert(const T& data)
	
		Node* pRoot = GetRoot();

		// 如果为空树, 则插入头节点并且为黑色
		if (nullptr == pRoot)
		
			pRoot = new Node(data, BLACK);
			pRoot->_pParent = pHead;
			return true;
		
		// 非空树
		else
		
			// 1.查找待插入节点在红黑树中的位置
			Node* pCur = pRoot;
			Node* pParent = nullptr;
			while (pCur)
			
				pParent = pCur;
				if (data < pCur->_data)
				
					pCur = pCur->_pLeft;
				
				else if (data > pCur->_data)
				
					pCur = pCur->_pRight;
				
				else
				
					return false;
				
			

			// 2. 插入新节点,调整使其满足红黑树的性质
			pCur = new Node(data);
			if (data < pParent->_data)
			
				pParent->_pLeft = pCur;
			
			else
			
				pParent->_pRight = pCur;
			
			pCur->_pParent = pParent;

			// 3. 红黑树性质可能遭到破坏
			// 插入的新节点默认为红色
			//   看pCur双亲是否时黑色,如果是黑色,则没有破坏性质
			//                           不是黑色,破坏了性质3,需要调整
			// 子树调整完之后,可能破坏了上面树的性质,所以还需要向上调整
			while (pParent != _pHead && RED == pParent->_color)
			
				Node* granderFather = pParent->_pParent;

				// 情况分为三种
				if (pParent == granderFather->_pLeft)
				
					Node* uncle = granderFather->_pRight;

					// 情况一: cur为红, pParent为红,grand为黑,uncle存在且为红
					// 该情况下,只需要判断叔叔节点,引为其它节点颜色一定满足,因为他们之前是一颗满足条件的红黑树
					if (uncle && RED == uncle->_color)
					
						// 只需要调整颜色即可
						// 祖父节点黑色,父节点与叔叔节点调整为红色
						granderFather->_color = RED;
						pParent->_color = BLACK;
						uncle->_color = BLACK;

						// 移动节点位置,继续向上调整
						pCur = granderFather;
						pParent = pCur->_pParent;
					
					else
					
						// 情况二 三:cur为红,parent为红,grander为黑,u不存在或者为黑

						// 情况二:cur为parent的左子树
						// 情况三:cur为parent的右子树-->旋转一下,调整为parent的左子树即可
						if (pCur == pParent->_rRight)
						
							RotateL(pParent);
							std::swap(pParent, pCur);
						

						// 调整颜色后旋转 使其满足性质4
						pParent->_color = BLACK;
						granderFather->_color = RED;
						RotateR(granderFather);
					
				
				else
				
					// 反着的情况一二三
					Node* uncle = granderFather->_pLeft;

					if (uncle && RED == uncle->_color)
					
						// 只需要调整颜色即可
						// 祖父节点黑色,父节点与叔叔节点调整为红色
						granderFather->_color = RED;
						pParent->_color = BLACK;
						uncle->_color = BLACK;

						// 移动节点位置,继续向上调整
						pCur = granderFather;
						pParent = pCur->_pParent;
					
					else
					
						if (pCur == pParent->_rLeft)
						
							RotateL(pParent);
							std::swap(pParent, pCur);
						

						// 调整颜色后旋转 使其满足性质4
						pParent->_color = BLACK;
						granderFather->_color = RED;
						RotateR(granderFather);
					
				
			
		

		// 根节点一定要为黑色
		pRoot->_color = BLACK;
		_pHead->_pLeft = LeftMost();
		_pHead->_pRight = RightMost();

		return true;
	

	// 检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
	Node* Find(const T& data)
	
		Node* pCur = GetRoot();

		while (pCur)
		
			if (data < pCur->_data)
			
				pCur = pCur->_pLeft;
			
			else if (data > pCur->_data)
			
				pCur = pCur->_pRight;
			
			else
			
				return pCur;
			
		

		return nullptr;
	

	// 获取红黑树最左侧节点
	Node* LeftMost()
	
		Node* pCur = GetRoot();
		if (nullptr == pCur)
		
			return _pHead;
		

		while (pCur->_pLeft)
		
			pCur = pCur->_pLeft;
		

		return pCur;
	

	// 获取红黑树最右侧节点
	Node* RightMost()
	
		Node* pCur = GetRoot();
		if (nullptr == pCur)
		
			return _pHead;
		

		while (pCur->_pRight)
		
			pCur = pCur->_pRight;
		

		return pCur;
	

	// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠_IsValidRBTRee函数检测
	bool IsValidRBTRee()
	
		Node* pRoot = GetRoot();

		// 空树满足红黑树的性质
		if (nullptr == pRoot)
		
			return true;
		

		if (BLACK != pRoot->_color)
		
			cout << "违反性质2:根节点应为黑色" << endl;
			return false;
		

		// 检测性质4:每条路径的黑色节点应当一致
		// 获取一条路径的黑色节点个数
		Node* pCur = root;
		size_t blackCount = 0;
		while (pCur)
		
			if (BLACK == pCur->_color)
			
				blackCount++;
			

			pCur = pCur->_pLeft;
		

		// 检测其它路径黑色节点的个数
		return _IsValidRBTRee(pRoot, blackCount, 0);
	

private:
	bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
	
		if (nullptr == pRoot)
		
			return true;
		

		Node* pParent = pRoot->_pParent;
		// 顺便检测性质三
		if (pParent != _pHead && RED == pParent->_color && RED == pRoot->_color)
		
			cout << "违反性质3:出现了连在一起的红色节点" << endl;
			return false;
		

		if (BLACK == pRoot->_color)
		
			pathBlack++;
		

		// pRoot已经遍历到了某条路径的末尾
		if (nullptr == pRoot->_pLeft && nullptr == pRoot->_pRight)
		
			if (pathBlack != blackCount)
			
				cout << "违反性质4:每条路径黑色节点个数不相同" << endl;
				reutrn false;
			
		

		return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack) && _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
	

	// 左单旋
	void RotateL(Node* pParent)
	
		Node* subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;

		pParent->_pRight = subRL;
		if (subRL)
		
			subRL->_pParent = pParent;
		

		subR->_pLeft = pParent;

		Node* ppParent = pParent->_pParent;
		pParent->_pParent = subR;
		subR->_pParent = ppParent;

		if (ppParent == _pHead)
		
			_pHead->_pParent = subR;
		
		else
		
			if (pParent == ppParent->_pLeft)
			
				ppParent->_pLeft = subR;
			
			else
			
				ppParent->_pRight = subR;
			
		
	

	// 右单旋
	void RotateR(Node* pParent)
	
		Node* subL = pParent->_pLeft;
		Node* subLR = subL->_pRight;

		pParent->_pLeft = subRL;
		if (subRL)
		
			subRL->_pParent = pParent;
		

		subR->_pRight = pParent;

		Node* ppParent = pParent->_pParent;
		pParent->_pParent = subR;
		subR->_pParent = ppParent;

		if (ppParent == _pHead)
		
			_pHead->_pParent = subR;
		
		else
		
			if (pParent == ppParent->_pRight)
			
				ppParent->_pRight = subR;
			
			else
			
				ppParent->_pLeft = subR;
			
		
	

	// 为了操作树简单起见:获取根节点
	Node*& GetRoot()
	
		return _pHead->_pParent;
	

private:
	Node* _pHead;
;

以上是关于[数据结构] 红黑树的详解的主要内容,如果未能解决你的问题,请参考以下文章

高阶数据结构(贰)——红黑树的基本概念和插入的实现

详解 AVL 树和红黑树的区别

删除红黑树的整个子树会保留其属性吗?

教你轻松理解红黑树的实现及原理

TreeMap红黑树源码详解

数据结构 - 红黑树(Red Black Tree)删除详解与实现(Java)