【数据结构】红黑树

文章目录

  • [1. 红黑树的概念](#1. 红黑树的概念)
  • [2. 红黑树的性质](#2. 红黑树的性质)
  • [3. 红黑树的实现](#3. 红黑树的实现)
    • [3.1 节点与树的定义](#3.1 节点与树的定义)
    • [3.2 查找](#3.2 查找)
    • [3.3 插入(重点)](#3.3 插入(重点))
    • [3.4 红黑树的验证](#3.4 红黑树的验证)
  • [4. 红黑树与AVL树的比较](#4. 红黑树与AVL树的比较)
  • 5.完整代码

1. 红黑树的概念

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

2. 红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(即不能出现连续的红节点)
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(即每条路径上的黑色节点相等
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍(最长路径 <= 最短路径×2 ),为什么会这样呢?-- 如下图

如果比较AVL树和红黑树的搜索,AVL树的查找效率更高为:logN;

但是在插入和删除时红黑树的效率更高,它不需要严格的平衡,旋转的次数更少。

3. 红黑树的实现

3.1 节点与树的定义

插入时,我们是插入黑节点好还是红节点好呢?选择破坏性质3还是性质4呢?

  • 如果插入黑色的,每条路径上的黑色节点数量就不相等了,每条路径都得改,非常麻烦。
  • 如果插入红色的,如果它的父亲是黑色的,那就没有破坏任何规则 ;若父亲是红色,那就存在了连续的红节点,最长路径可能就会超过最短路径的二倍,需要进行调整了。

那我们肯定选择调整次数少的方式,因此新增节点的颜色给红色(注意:当插入节点为根节点时,应该是黑色)

  • 节点的结构
cpp 复制代码
	//颜色
	enum Color
	{
		RED = 0,
		BLACK
	};

	template<class K, class V>
	struct TreeNode
	{
		pair<K, V>  _kv;
		TreeNode<K, V>* _left;
		TreeNode<K, V>* _right;
		TreeNode<K, V>* _parent;//前驱节点
		Color _col;

		TreeNode(const pair<K, V>& kv)
			:_kv(kv)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _col(RED)//默认新节点为红色的
		{}
	};
  • 树的结构
cpp 复制代码
	template<class K,class V>
	class RBTree
	{
		typedef TreeNode<K, V> Node;
	public:
		RBTree()
			:_root(nullptr)
		{}
		RBTree(const RBTree<K, V>& tree)
		{
			this->_root = Copy(tree._root);
		}
		RBTree<K, V>& operator=(RBTree<K, V> tree)
		{
			swap(this->_root, tree._root);//现代写法
			return *this;
		}
		~RBTree()
		{
			Destroy(_root);
		}

		bool insert(const pair<K, V>& kv)
		{

		}

		Node* find(const pair<K, V>& kv)
		{

		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

	private:
		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}

		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;
			_root->_left = Copy(root->_left);
			_root->_right = Copy(root->_right);
			return _root;
		}
		
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_kv.first << " ";
			_InOrder(root->_right);
		}

		Node* _root;
	};

3.2 查找

对于查找功能,搜索树都是一样的,非常简单就不赘述了。

cpp 复制代码
		Node* find(const pair<K, V>& kv)
		{
			Node* cur = _root;

			while (cur)
			{
				if (cur->_kv.first < kv.first)
					cur = cur->_right;
				else if (cur->_kv.first > kv.first)
					cur = cur->_left;
				else
					return  cur;
			}
			return nullptr;
		}

3.3 插入(重点)

在插入时,我们要先按照搜索树的规则找到插入位置,然后将新节点插入;新节点插入后,要检查红黑树的规则有没有被破坏。注意:如果新插入的节点就是根节点,需要遵守规则2,根节点应为黑色

cpp 复制代码
		bool insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				//如果插入的节点是头节点,需要遵守规则2(根是黑的)
				//需要将默认的红色改为黑
				_root->_col = BLACK;
				return true;
			}
			
			Node* cur = _root;
			Node* parent = cur->_parent;
			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);
			cur->_parent = parent;
			//父亲连孩子
			if (parent->_kv.first < kv.first)
				parent->_right = cur;
			else
				parent->_left = cur;

			//检查是否违反红黑树的规则
			//....
		}

检测新节点插入后,红黑树的性质是否造到破坏;因为新节点的默认颜色是红色,因此,

  • 如果其父亲节点的颜色是黑色,没有违反红黑树任何规则,则不需要调整
  • 但当其父亲节点颜色为红色时,就违反了性质三:不能有连在一起的红色节点

此时需要对红黑树分情况来讨论:

那违反规则后如何调整呢?又分为一下几种情况:

  1. 若新增节点为红,父亲为红,爷爷为黑,叔叔存在且为红,需要变色处理

但是当爷爷节点不是子树而是根时,需要将其变成黑色;若是子树,则保持它的红色,继续向上调整。

  1. 若新增节点为红,父亲为红,爷爷为黑,叔叔不存在或者叔叔存在且为黑,需要旋转+变色处理

右单旋的情况

  • 父亲在爷爷的左边、新增在父亲的左边
  • 如果叔叔不存在,旋转后将父亲变黑,爷爷变红
  • 如果叔叔存在且为黑,旋转后将父亲变黑,爷爷变红

新增节点为红色的原因如下:


左右双旋的情况

  • 父亲在爷爷的左边、新增在父亲的右边
  • 如果叔叔不存在,旋转后将新增节点变黑,爷爷变红
  • 如果叔叔存在且为黑,旋转后将新增节点变黑,爷爷变红

由于旋转的具体代码我们在AVL树种已经写过,这里就不给出具体步骤了。

cpp 复制代码
	private:
		void RotateR(Node* parent)
		{
			Node* SubL = parent->_left;
			Node* SubLR = SubL->_right;

			//降级连兵
			parent->_left = SubLR;
			if (SubLR)
				SubLR->_parent = parent;
			Node* parentParent = parent->_parent;
			//升级连将
			SubL->_right = parent;
			parent->_parent = SubL;

			//将连将
			SubL->_parent = parentParent;
			if (parentParent == nullptr)
			{
				_root = SubL;
			}
			else
			{
				if (parentParent->_left == parent)
					parentParent->_left = SubL;
				else
					parentParent->_right = SubL;
			}
		}

		void RotateL(Node* parent)
		{
			Node* SubR = parent->_right;
			Node* SubRL = SubR->_left;

			//降级连兵
			parent->_right = SubRL;
			if (SubRL)
				SubRL->_parent = parent;
			Node* parentParent = parent->_parent;
			//升级连将
			SubR->_left = parent;
			parent->_parent = SubR;

			//将连将
			SubR->_parent = parentParent;
			if (parentParent == nullptr)
				_root = SubR;
			else
			{
				if (parentParent->_left == parent)
					parentParent->_left = SubR;
				else
					parentParent->_right = SubR;
			}
		}

当父亲在爷爷左侧时,大体框架就是这样:

当父亲在爷爷的右侧时,也是类似的,我们仅给出旋转时的图:

新增在父亲的右侧,构成"直"树,采用左单旋


右左双旋

新增在父亲的左侧,构成"弯"树,采用右左双旋

具体的代码实现如下:

cpp 复制代码
				//grandfather->_right == parent
				else
				{
					Node* uncle = grandfather->_left;
					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//继续调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//    g
						// u     p
						//           c
						if (parent->_right == cur)
						{
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//      g
						//   u      p
						//	      c
						else
						{
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;
					}

那红黑树的插入就实现完了,完整代码如下:

cpp 复制代码
		bool insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				//如果插入的节点是头节点,需要遵守规则2(根是黑的)
				//需要默认的红色改为黑
				_root->_col = BLACK;
				return true;
			}

			Node* cur = _root;
			Node* parent = cur->_parent;
			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);
			cur->_parent = parent;
			//父亲连孩子
			if (parent->_kv.first < kv.first)
				parent->_right = cur;
			else
				parent->_left = cur;
			//-----------------------------------------------------------------------
			//检查是否违反红黑树的规则
			//如果父亲存在且为红,则违反规则
			while (parent && parent->_col == RED)
			{
				Node* grandfather = parent->_parent;
				if (grandfather->_left == parent)
				{
					//根据爷爷节点找叔叔
					Node* uncle = grandfather->_right;

					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//然后继续向上调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//判断单旋(直树)还是双旋(弯树)
						//单旋
						if (parent->_left == cur)
						{	
							//       g
							//     p   u
							//	c
							RotateR(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//双旋
						else
						{
							//        g
							//     p     u
							//	     c
							RotateL(parent);
							RotateR(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;

						}
						break;//旋转后已经符合规则,无需再调整了
					}
				}
				//grandfather->_right == parent
				else
				{
					Node* uncle = grandfather->_left;
					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//继续调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//    g
						// u     p
						//           c
						if (parent->_right == cur)
						{
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//      g
						//   u      p
						//	      c
						else
						{
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;
					}
				}
			}
			//父亲不存在,或者父亲为黑时,无需判断,直接将根变黑
			_root->_col = BLACK;
			return true;
		}

3.4 红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
    • 是否存在连续的红节点
    • 每条路径黑色节点的个数是否相等

在检查每条路径上黑节点的个数时,首先要有一个黑节点个数的标准值,可以是最左侧或者最右侧;同时还要有一个变量或者容器来存储当前路径中表黑节点的个数。

cpp 复制代码
		bool IsRBTree()
		{
			Node* root = _root;
			if (root == nullptr)//空树也是
				return true;
			
			if (root->_col == RED)
			{
				cout << "根节点为红色,违反规则" << endl;
				return false;
			}

			//先统计最左侧路径上有多少黑节点作为标准
			Node* cur = root;
			int blackNum = 0;
			while (cur)
			{
				if (cur->_col == BLACK)
					blackNum++;
				cur = cur->_left;
			}
			//k用来记录路径中黑节点的个数
			int k = 0;
			//在依据标准黑节点的个数,比较其它路径
			return _IsRBTree(root, blackNum, k);
		}

有了黑节点的标准值后,我们才可正式开始比较每条路径黑节点的个数

cpp 复制代码
		bool _IsRBTree(Node* root, int blackNum, int k)
		{
			//一条路径走完,比较黑节点的个数
			if (root == nullptr)
			{
				if (blackNum != k)
				{
					cout << "路径黑节点个数不相等,违反规则" << endl;
					return false;
				}
				return true;
			}
			Node* parent = root->_parent;
			if (parent && root->_col == RED && parent->_col == RED)
			{
				cout << "存在连续的红节点,违反规则" << endl;
				return false;
			}

			//统计黑节点
			if (root->_col == BLACK)
				k++;
			//统计完根,在依次统计左右子树
			return _IsRBTree(root->_left, blackNum, k)
				&& _IsRBTree(root->_right, blackNum, k);
		}

4. 红黑树与AVL树的比较

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

5.完整代码

cpp 复制代码
namespace my
{
	//颜色
	enum Color
	{
		RED = 0,
		BLACK
	};

	template<class K, class V>
	struct TreeNode
	{
		pair<K, V>  _kv;
		TreeNode<K, V>* _left;
		TreeNode<K, V>* _right;
		TreeNode<K, V>* _parent;//前驱节点
		Color _col;

		TreeNode(const pair<K, V>& kv)
			:_kv(kv)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _col(RED)//默认新节点为红色的
		{}
	};

	template<class K,class V>
	class RBTree
	{
		typedef TreeNode<K, V> Node;
	public:
		RBTree()
			:_root(nullptr)
		{}
		RBTree(const RBTree<K, V>& tree)
		{
			this->_root = Copy(tree._root);
		}
		RBTree<K, V>& operator=(RBTree<K, V> tree)
		{
			swap(this->_root, tree._root);//现代写法
			return *this;
		}
		~RBTree()
		{
			Destroy(_root);
		}

		bool insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				//如果插入的节点是头节点,需要遵守规则2(根是黑的)
				//需要默认的红色改为黑
				_root->_col = BLACK;
				return true;
			}

			Node* cur = _root;
			Node* parent = cur->_parent;
			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);
			cur->_parent = parent;
			//父亲连孩子
			if (parent->_kv.first < kv.first)
				parent->_right = cur;
			else
				parent->_left = cur;

			//检查是否违反红黑树的规则
			//如果父亲存在且为红,则违反规则
			while (parent && parent->_col == RED)
			{
				Node* grandfather = parent->_parent;
				if (grandfather->_left == parent)
				{
					//根据爷爷节点找叔叔
					Node* uncle = grandfather->_right;

					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//然后继续向上调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//判断单旋(直树)还是双旋(弯树)
						//单旋
						if (parent->_left == cur)
						{	
							//       g
							//     p   u
							//	c
							RotateR(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//双旋
						else
						{
							//        g
							//     p     u
							//	     c
							RotateL(parent);
							RotateR(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;

						}
						break;//旋转后已经符合规则,无需再调整了
					}
				}
				//grandfather->_right == parent
				else
				{
					Node* uncle = grandfather->_left;
					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//继续调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//    g
						// u     p
						//           c
						if (parent->_right == cur)
						{
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//      g
						//   u      p
						//	      c
						else
						{
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;
					}
				}
			}
			//父亲不存在,或者父亲为黑时,无需判断,直接将根变黑
			_root->_col = BLACK;
			return true;
		}

		Node* find(const pair<K, V>& kv)
		{
			Node* cur = _root;

			while (cur)
			{
				if (cur->_kv.first < kv.first)
					cur = cur->_right;
				else if (cur->_kv.first > kv.first)
					cur = cur->_left;
				else
					return  cur;
			}
			return nullptr;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

		bool IsRBTree()
		{
			Node* root = _root;
			if (root == nullptr)//空树也是
				return true;
			
			if (root->_col == RED)
			{
				cout << "根节点为红色,违反规则" << endl;
				return false;
			}

			//先统计最左侧路径上有多少黑节点作为标准
			Node* cur = root;
			int blackNum = 0;
			while (cur)
			{
				if (cur->_col == BLACK)
					blackNum++;
				cur = cur->_left;
			}
			//k用来记录路径中黑节点的个数
			int k = 0;
			//在依据标准黑节点的个数,比较其它路径
			return _IsRBTree(root, blackNum, k);
		}

		int Size()
		{
			return _Size(_root);
		}

		int Height()
		{
			return _Height(_root);
		}

	private:
		int _Height(Node* root)
		{
			if (root == nullptr)
				return 0;
			int leftH = _Height(root->_left);
			int rightH = _Height(root->_right);

			return leftH > rightH ? leftH + 1 : rightH + 1;
		}

		int _Size(Node* root)
		{
			if (root == nullptr)
				return 0;

			return 1 + _Size(root->_left) + _Size(root->_right);
		}

		bool _IsRBTree(Node* root, int blackNum, int k)
		{
			//一条路径走完,比较黑节点的个数
			if (root == nullptr)
			{
				if (blackNum != k)
				{
					cout << "路径黑节点个数不相等,违反规则" << endl;
					return false;
				}
				return true;
			}
			Node* parent = root->_parent;
			if (parent && root->_col == RED && parent->_col == RED)
			{
				cout << "存在连续的红节点,违反规则" << endl;
				return false;
			}

			//统计黑节点
			if (root->_col == BLACK)
				k++;
			//统计完根,在依次统计左右子树
			return _IsRBTree(root->_left, blackNum, k)
				&& _IsRBTree(root->_right, blackNum, k);
		}

		void RotateR(Node* parent)
		{
			Node* SubL = parent->_left;
			Node* SubLR = SubL->_right;

			//降级连兵
			parent->_left = SubLR;
			if (SubLR)
				SubLR->_parent = parent;
			Node* parentParent = parent->_parent;
			//升级连将
			SubL->_right = parent;
			parent->_parent = SubL;

			//将连将
			SubL->_parent = parentParent;
			if (parentParent == nullptr)
			{
				_root = SubL;
			}
			else
			{
				if (parentParent->_left == parent)
					parentParent->_left = SubL;
				else
					parentParent->_right = SubL;
			}
		}

		void RotateL(Node* parent)
		{
			Node* SubR = parent->_right;
			Node* SubRL = SubR->_left;

			//降级连兵
			parent->_right = SubRL;
			if (SubRL)
				SubRL->_parent = parent;
			Node* parentParent = parent->_parent;
			//升级连将
			SubR->_left = parent;
			parent->_parent = SubR;

			//将连将
			SubR->_parent = parentParent;
			if (parentParent == nullptr)
				_root = SubR;
			else
			{
				if (parentParent->_left == parent)
					parentParent->_left = SubR;
				else
					parentParent->_right = SubR;
			}
		}
		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}

		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;
			_root->_left = Copy(root->_left);
			_root->_right = Copy(root->_right);
			return _root;
		}

		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_kv.first << " ";
			_InOrder(root->_right);
		}

		Node* _root;
	};
}
相关推荐
别NULL4 小时前
机试题——疯长的草
数据结构·c++·算法
ZSYP-S5 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
唐叔在学习5 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA6 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
武昌库里写JAVA9 小时前
浅谈怎样系统的准备前端面试
数据结构·vue.js·spring boot·算法·课程设计
S-X-S9 小时前
代码随想录刷题-数组
数据结构·算法
l138494274519 小时前
每日一题(4)
java·数据结构·算法
kyrie_sakura9 小时前
c++数据结构算法复习基础--13--基数算法
数据结构·c++·算法
XWXnb69 小时前
数据结构:顺序表
数据结构·算法
橘颂TA9 小时前
【C++】数据结构 顺序表的实现(详解)
开发语言·数据结构·c++·算法