C++---红黑树介绍及简单实现

目录

1. 红黑树

1.1 红黑树的概念

1.2 红黑树的性质

1.3 红黑树节点的定义

1.4 红黑树的插入操作

1.5 红黑树的验证

1. 红黑树

1.1 红黑树的概念

        红黑树是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍
,因而是接近平衡的。

1.2 红黑树的性质

1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(即没有连续红节点
4. 每条路径的黑色节点数目相等。
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,即上图中的 NIL 节点)。

        只要满足上面的性质,红黑树就能保证其最长路径中节点个数不会超过最短路径节点
个数的两倍。其中最短路径一定是连续黑色节点,最长路径一定是红黑节点交替出现。

1.3 红黑树节点的定义

        因为节点颜色只有红黑两种,所以我们把表示节点颜色的变量用枚举类型来表示,另外节点用三叉链表示:即含有三个指针分别指向左右孩子即父节点。还需一个键值对表示节点的key,value值。

enum Color
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode* _left;//指向左子树
	RBTreeNode* _right;//指向右子树
	RBTreeNode* _parent;指向父节点

	pair<K, V> _kv;//节点值
	Color _col;//颜色

	RBTreeNode(const pair<K, V>& kv)//拷贝构造
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
	{}
};

1.4 红黑树的插入操作

思路如下:

首先:插入节点应该是什么颜色:黑色 or 红色

        这里我们选择插入红色节点。理由:若插入黑色节点,因为要满足所有路径黑色节点都相同,整棵树直接就不满足了,需要改动很大,不可取;而若是插入红色节点,首先黑色节点数目不被影响,那么接下来只需要保证没有连续红色节点出现就行,我们可以通过从该节点往根节点方向遍历实现。

接下来,我们在默认插入节点为红色的情况下分类讨论插入情况 :

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

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

        这里 a, b, c, d, e 可以为空也可以为子树,若为空,则cur就是要插入节点,若为子树,则子树要使得整棵树满足红黑树规则:每条节点黑色数目相同。 

要满足不能出现连续红节点,我们需要把 p 节点改为黑,又因为因为要满足每条路径黑色节点数目相等,则需要在把 p 改为黑的同时,把u节点改为黑把 g 节点改为红。

 接下来,只需要看 g 节点的父节点情况:

        如果g节点为根,只需要把g节点改为黑即可;

        如果g是子树,则一定有父节点,其父节点若为黑就不管,若为红继续向上调整。

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

 说明:此处若p为g左孩子,则cur为p左孩子;若p为g右孩子,则cur为p右孩子。

若 u 不存在,则需要右旋转+变色:

 若 u 存在,情况一样:

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


  说明:此处若p为g左孩子,则cur为p右孩子;若p为g右孩子,则cur为p左孩子。

若u不存在:

 若u存在且为黑:

 代码如下:

template<class K, class V>
struct RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	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走到要插入的位置
		cur = new Node(kv);
		cur->_col = RED;//插入节点要为红
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//变色
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			assert(grandfather->_col == BLACK);
			//关键看叔叔
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//情况一:uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else//情况二+三:uncle不存在+存在且为黑
				{
					//     g
					//   p   u
					// c
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//   p   u
						//    c
						RotateL(parent);//以p为轴点左旋
						RotateR(grandfather);//以g为轴点右旋
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else//parent == grandfather->_right
			{
				Node* uncle = grandfather->_left;
				//情况一
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else//情况二+三:uncle不存在+存在且为黑
				{
					//     g
					//   u   p
					//         c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//   u   p
						//      c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		//根节点一定为黑
		_root->_col = BLACK;
		return true;
	}

private:
	
	void RotateL(Node* parent)//左旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)//subRL可以为空,所以加条件
		{
			subRL->_parent = parent;
		}
		//parnet 可能为根也可以不是,记录一下
		Node* ppNode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
	}
	void RotateR(Node* parent)//右旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)//subLR可以为空,所以加条件
		{
			subLR->_parent = parent;
		}
		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
	}
private:
	Node* _root = nullptr;
};

1.5 红黑树的验证

红黑树验证思路有两种:

1. 看最长路径节点数是否超过最短路径节点数2倍。

2. 看每条路径黑色节点数目是否相等。

那么我们应该选择那种方式呢?

        这里我们选择第二种因为如果最长路径节点数不超过最短路径节点数2倍,并不一定满足颜色规则;相反,如果满足颜色规则,则一定满足最长路径节点数不超过最短路径节点数2倍

验证代码如下:

bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}
		if (_root->_col == RED)
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}

		//基准值
		int benchmark = 0;
		Node* cur = _root;
		while (cur)//选择最左路径计算黑色节点数目作为基准值
		{
			if (cur->_col == BLACK)
				++benchmark;

			cur = cur->_left;
		}

		return PrevCheck(_root, 0,benchmark);
	}
private:
	//blackNum记录根节点到当前节点的黑色节点数量
	bool PrevCheck(Node* root, int blackNum,int benchmark)
	{
		if (root == nullptr)
		{
			if (blackNum != benchmark)
			{
				cout << "某条黑色节点数量不相等" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}
		if (root->_col == BLACK)
		{
			++blackNum;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "存在连续红色节点" << endl;
			return false;
		}

		return PrevCheck(root->_left, blackNum,benchmark)
		    && PrevCheck(root->_right, blackNum,benchmark);
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值