C++红黑树

发布于:2024-09-19 ⋅ 阅读:(13) ⋅ 点赞:(0)

目录

一、红黑树的概念

二、红黑树的性质

三、红黑树节点的定义

四、红黑树的插入

1. 按照二叉搜索的树规则插入新节点

2. 检测新节点插入后,红黑树的性质是否造到破坏

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

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

五、红黑树的验证

六、红黑树和AVL树的比较


一、红黑树的概念

        红黑树,是一种二叉搜索树,但是在每个节点上增加了一个存储位表示节点的颜色,可以是RED或者BLACK。通过对任何一条从根到叶子的路径上各个节点颜色的某种限制,红黑树保证了没有一条路径会比最短路径长出两倍,所以红黑树是一种接近平衡的搜索二叉树。

二、红黑树的性质

1.每个节点不是红色就是黑色

2.根节点是黑色的

3.如果一个节点是红色的,那么他的两个孩子节点是黑色的(即不能有连续的红色节点)

4.对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含一样数量的黑色节点

5.每个叶子节点都是黑色的(这里的叶子节点指的是空节点,也叫NIL节点)

三、红黑树节点的定义

	enum Color
	{
		RED,BLACK
	};
	template<class K,class V>
	struct RBTreeNode
	{
		typedef RBTreeNode<K, V> Node;
		Node* _left;
		Node* _right;
		Node* _parent;
		pair<K, V> _kv;
		Color _col;
		//构造函数
		RBTreeNode(const pair<K,V>& kv)
			:_left(nullptr)
			,_right(nullptr)
			,_parent(nullptr)
			,_kv(kv)
			,_col(RED)
		{}

	};

        为什么红黑树的节点默认是红色呢?因为如果插入黑色节点,会违反规则4,但是插入红色节点,只有可能影响规则3(不能有连续的红色节点),调整起来比较方便。

四、红黑树的插入

1. 按照二叉搜索的树规则插入新节点

template<class ValueType>
class RBTree
{
  //……
bool Insert(const ValueType& data)
{
PNode& pRoot = GetRoot();
if (nullptr == pRoot)
{
pRoot = new Node(data, BLACK);
// 根的双亲为头节点
pRoot->_pParent = _pHead;
      _pHead->_pParent = pRoot;
}
else
{
// 1. 按照二叉搜索的树方式插入新节点
      // 2. 检测新节点插入后,红黑树的性质是否造到破坏,
//  若满足直接退出,否则对红黑树进行旋转着色处理
}
// 根节点的颜色可能被修改,将其改回黑色
pRoot->_color = BLACK;
_pHead->_pLeft = LeftMost();
_pHead->_pRight = RightMost();
return true;
}
private:
PNode& GetRoot(){ return _pHead->_pParent;}

private:
PNode _pHead;

2. 检测新节点插入后,红黑树的性质是否造到破坏

        因为新节点的默认颜色是红色,因此,:如果其父亲节点是黑色,就没有违反红黑树的性质,则不需要调整,可以直接插入。但是当父亲节点为红色的时候,则违反了性质三(不能有连续的红色节点),此时需要对红黑树进行分类讨论。

        约定:cur为当前节点,p为父节点,g为祖父节点,uncle为叔叔节点

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

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

        但是对于情况二:我们需要分类讨论,因为cur在parent的左边和右边/parent在祖父的左边和右边所产生的旋转是不同的,一共需要分成4类来讨论

template<class K,class V>
	class RBTree
	{
		typedef RBTreeNode<K, V> Node;
	public:
		bool insert(const pair<K,V>& kv)
		{
			//先按照普通搜索二叉树的规则插入节点
			if (_root==nullptr)
			{
				_root = new Node(kv);
				return true;
			}
			else
			{
				//找到要插入的位置
				Node* cur = _root;
				Node* parent = cur->_parent;
				while (cur!=nullptr)
				{
					if (kv.first<cur->_kv.first)
					{
						parent = cur;
						cur = cur->_left;
					}
					else if (kv.first>cur->_kv.first)
					{
						parent = cur;
						cur = cur->_right;
					}
					else if (kv.first==cur->_kv.first)
					{
						return false;
					}
				}
				//cur是连接到parent的左边还是右边呢
				cur = new Node(kv);
				if (cur->_kv.first<parent->_kv.first)
				{
					parent->_left = cur;
					cur->_parent = parent;
				}
				else if (cur->_kv.first>parent->_kv.first)
				{
					parent->_right = cur;
					cur->_parent = parent;
				}
				//根据红黑树的规则进行变色+旋转
				//只有当父亲是红色且存在,才需要处理,因为父亲是黑色对上面没有影响可以直接插入
				while (parent!=nullptr&&parent->_col==RED)
				{
					Node* grandparent = parent->_parent;
					//parent==grandparent->_left
					if (parent==grandparent->_left)
					{
						Node* uncle = grandparent->_right;
						//叔叔存在且为红色
						if (uncle!=nullptr&&uncle->_col==RED)
						{
							//变色
							parent->_col = uncle->_col = BLACK;
							grandparent->_col = RED;
							//grandparent给cur继续向上调整
							cur = grandparent;
							parent = grandparent->_parent;
						}
						//叔叔不存在或者存在且为黑色
						else
						{
							if (cur==parent->_left)
							{
								//右单旋
								_RotateR(grandparent);
								//变色
								grandparent->_col = RED;
								parent->_col = BLACK;

							}
							else
							{
								//双旋
								_RotateL(parent);
								_RotateR(grandparent);
								cur->_col = BLACK;
								grandparent->_col = RED;
							}
							//因为走到else结束后,parent右一种情况是是黑色,有一种情况是红色,但是都以及调整好了,
							//不需要再往上调整,所以直接break
							break;
						}
					}
					//parent==grandparent->_right
					else
					{
						Node* uncle = grandparent->_left;
						if (uncle!=nullptr&&uncle->_col==RED)
						{
							//变色
							uncle->_col = parent->_col = BLACK;
							grandparent->_col = RED;
							//继续往上处理
							cur = grandparent;
							parent = grandparent->_parent;
						}
						else
						{
							if (cur==parent->_right)
							{
								//左旋
								_RotateL(grandparent);
								parent->_col = BLACK;
								grandparent->_col = RED;
							}
							else
							{
								//双旋
								_RotateR(parent);
								_RotateL(grandparent);
								//变色
								cur->_col = BLACK;
								grandparent->_col = RED;

							}
							break;
						}
					}
				}
			}
			//根必须要是黑色
			_root->_col = BLACK;
			return true;
		}



		//右单旋
		void _RotateR(Node* parent)
		{
			//记录两个重要节点
			Node* subL = parent->_left;
			Node* subLR = subL->_right;

			//连接
			parent->_left = subLR;
			//如果subL的右边为空,空不能解引用,需要注意一下
			if (subLR->_right != nullptr)
			{
				subLR->_parent = parent;
			}
			subL->_right = parent;
			//处理根的问题
			Node* grandparent = parent->_parent;

			parent->_parent = subL;
			subL->_parent = grandparent;
			//如果原本parent是根,则让_root=subL
			if (parent == _root)
			{
				_root = subL;
				subL->_parent = nullpptr;
			}
			//如果parent是子树,则连接subL和grandparent
			else
			{
				if (grandparent->_right == parent)
				{
					grandparent->_right = subL;
				}
				else if (grandparent->_left == parent)
				{
					grandparent->_left = subL;
				}
			}

		}
		//左单旋
		void _RotateL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			parent->_right = subRL;
			subR->_left = parent;

			Node* grandparent = parent->_parent;
			parent->_parent = subR;
			if (subRL != nullptr)
			{
				subRL->_parent = parent;
			}
			//处理根的问题
			if (parent == _root)
			{
				_root = subR;
				subR->_parent = nullptr;
			}
			else
			{
				if (parent == grandparent->_right)
				{
					grandparent->_right = subR;
				}
				else if (parent == grandparent->_left)
				{
					grandparent->_left = subR;
				}
			}

		}
	private:
		Node* _root = nullptr;
	};
}

五、红黑树的验证

        红黑树的验证检测分为两步:

(1)检测其是非满足二叉搜索树的规则(中序是否为有序)

(2)检测其是否满足红黑树的性质

bool isBalance()
		{
			if (_root==nullptr)
			{
				return true;
			}
			if (_root->_col==RED)
			{
				return false;
			}

			//求参考值
			int refval = 0;
			Node* cur = _root;
			while (cur!=nullptr)
			{
				if (cur->_col==BLACK)
				{
					refval++;
				}
				cur = cur->_left;
			}
			return Check(_root, 0, refval);
		}
		//检查颜色是否符合规则和黑色节点的数量是否相同
		//blacknum是根节点到当前节点路径上的黑色节点数量
		bool Check(Node* root,int blacknum,const int refVal)
		{
            //走到空的时候说明该路径已经走完了,比对balcknum的值
			if (root==nullptr)
			{
				if (blacknum!=refVal)
				{
					cout << "存在不同路径的黑色节点数量不相等" << endl;
					return false;
				}
				return true;
			}
			//因为检测孩子不好检测,有多种情况,所以我们反过来从孩子检测父亲的颜色
			if (root->_col==RED&& root->_parent->_col == RED)
			{
				cout << "有连续的红色节点" << endl;
				return false;
			}
			if (root->_col==BLACK)
			{
				blacknum++;
			}
			return Check(root->_left,blacknum,refVal)
				&& Check(root->_right,blacknum,refVal);
		}

六、红黑树和AVL树的比较

        红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O($log_2 N$),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,(不过像词典这种增删的比较少的项目,AVL由于其追求绝对平衡,所以查找的效率更高)而且红黑树实现比较简单,所以实际运用中红黑树更多。

        红黑树常常因为其高效的性能,而被用到map/set等数据结构中。


网站公告

今日签到

点亮在社区的每一天
去签到