C++实现红黑树

一.什么是红黑树

  红黑树是一种自平衡的二叉查找树,是一种高效的查找树。它是由 Rudolf Bayer 于1978年发明,在当时被称为平衡二叉 B 树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。

二.为什么需要红黑树

  对于二叉树,如果它是一棵接近平衡的二叉树,它的操作效率(查询,插入,删除)较高,其时间复杂度是O(logN)。但是可能会出现一种极端的情况,那就是插入的数据是有序的(递增或者递减),那么所有的节点都会在根节点的右侧或左侧,此时,二叉搜索树就变为了一个链表,它的操作效率就降低了,时间复杂度为O(N),所以可以认为二叉搜索树的时间复杂度介于O(logN)和O(N)之间,视情况而定。

  那么为了应对这种极端情况,红黑树就出现了,它是具备了某些特性的二叉搜索树,能解决非平衡树问题,红黑树是一种接近平衡的二叉树(说它是接近平衡因为它并没有像AVL树的平衡因子的概念,它只是靠着满足红黑节点的特殊性质来维持一种接近平衡的结构,进而提升整体的性能,并没有严格的卡定某个平衡因子来维持绝对平衡)。

  因此,红黑树相对于二叉树而言,其接近平衡的性质保证了它的时间复杂度,同时,它的控制条件只把它调整为接近平衡,因为对其的平衡条件相比于AVL树较为宽松,其旋转次数必然没有AVL树那么多,在一定情况下,发生的旋转次数必然比AVL树更少,其效率比AVL略微优秀。

三.红黑树的特性

    首先,红黑树是一个二叉搜索树,它在每个节点增加了一个存储位记录节点的颜色,可以是RED,也可以是BLACK;通过任意一条从根到叶子简单路径颜色的约束,当从根节点到叶子节点的路径上黑色节点相同时红黑树保证最长路径不超过最短路径的二倍,因而近似平衡。为了达到这个目的,它需要同时满足以下特性:

  1. 节点是红色或黑色
  2. 根是黑色
  3. 叶子节点都是黑色,这里的叶子节点指的是最底层的空节点(外部节点),下图中的那些null节点才是叶子节点,null节点的父节点在红黑树里不将其看作叶子节点。
  4. 红色节点的子节点都是黑色
    1.红色节点的父节点都是黑色
    2.从根节点到叶子节点的所有路径上不能有 2 个连续的红色节点
  5. 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点

那么它是如何根据特性保证,最长路径不超过最短路径的两倍的呢?

首先,由定义我们可以得知:

  这个树的最短路径上的节点一定全是黑色的,假设其节点数为n。

 证明如下:其实也没啥证明的,五个特性对黑色节点没有硬性要求,那么我们的最短路径就是纯黑色节点组成的。

  这个树的最长路径一定是n个黑色和n个红色节点组成的。

 证明如下:

   1.从第五特性得知,红黑树的所有路径都包含相同数目的黑色节点,那么它就有n个黑色节点

   2.如果他要是最长的路径,那么其它节点就为红色,但红色节点又不能连续存在,最多是一个红色节点和一个黑色节点交替存在

   3.同时我们要求最长路径,那么就是红色节点和黑色节点交替存在的情况,其红色节点数目和黑色节点数目便成了1:1的关系,因此我们便有n个红色节点。

示例如下:

C++实现红黑树_第1张图片

  四.红黑树的节点定义与旋转操作

 4.1 节点定义

   根据其红黑树的特性我们可知,每个节点我们都要有一个数据来表示颜色,因为这里只有红黑两种颜色,我建议用一个枚举类型来定义。

enum Color
{
	Red,
	Black
};

//直接实现kv模型的红黑树
template
class RBTreeNode
{
public:
	RBTreeNode(const pair& data)
		:_co(Red)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
	{}
	Color _co;
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	pair _data;
};

4.2 旋转操作

  我们都知道,旋转是一个二叉树的核心操作,这里我们直接提前写一下。

右单旋:(直接盗用AVL树的图片,以代码为准)
C++实现红黑树_第2张图片

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

	parent->_left = SubLR;
	if (SubLR) SubLR->_parent = parent;

	Node* pparent = parent->_parent;

	SubL->_right = parent;
	parent->_parent = SubL;

	if (!pparent)
	{
		_root = SubL;
		SubL->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = SubL;
		}
		else
		{
			pparent->_right = SubL;
		}
		SubL->_parent = pparent;
	}
}

 左单旋:(以代码为准)
C++实现红黑树_第3张图片

C++实现红黑树_第4张图片

void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	if (subRL)  
		subRL->_parent = parent;

	Node* pparent = parent->_parent;

	subR->_left = parent; //将subR的左指针指向parent
	parent->_parent = subR;//将parent的父指针指向subR

	if (!pparent ) //判断parent是否是头节点
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = subR;
		}
		else
		{
			pparent->_right = subR;
		}
		subR->_parent = ppnode;
	}
}

 五. 插入操作

  红黑树的插入过程和二叉查找树插入过程基本类似,不同的地方在于,红黑树插入新节点后,需要进行调整,以满足红黑树的性质。

   性质1规定红黑树节点的颜色要么是红色要么是黑色,那么在插入新节点时,这个节点应该是红色还是黑色呢?

   答案是红色。

   原因也不难理解。如果插入的节点是黑色,那么就必定出现 有一条路径的黑色节点数目变多了,那么这个该如何调整呢?显然是比较困难。

  那么我们如果插入红色节点呢,显然只影响性质4,只可能出现连续两个红色节点的情况,这个我们就需要引入变色和旋转来解决,但总归是比插入黑色好解决的多。

5.1 插入时的情况

    红黑树的插入,大部分情况都是看叔叔节点。

   插入时满足性质4,即不破坏红黑树的任何性质

 假设我们插入时,其父亲节点为黑色,那么我们就不用做调整,直接跳过。

  插入时不满足性质4时,我们一共有八种情况,其分为三类。

5.1.1 叔叔节点为红色 

   其形式如下:

 (p为parent节点,g为grand节点,c为cur节点,u为uncle节点)

C++实现红黑树_第5张图片

在这种情况下,我们不需要旋转,只需要变色就可以解决,我们将 

将p节点和u节点的颜色都改为黑色,然后将g节点的颜色改为红色,我们发现,其改变后这一部分不在违反红黑树的任何特性,但是其g之上的部分可能被影响(因为改变了g的颜色),所以说这种情况我们应该放在循环里面,我们将g赋值给p,将g->p赋值给p,然后循环在g上面的路径寻找破坏特性的部分。

代码如下: 

	//开始判断是否需要变色
	while (parent && parent->_co == Red)
	{
		Node* grand = parent->_parent;
		if (parent == grand->_left)
		{
			Node* uncle = grand->_right;
			if (uncle && uncle->_co == Red)
			{
				//这里只变色就好
				parent->_co = uncle->_co = Black;
				grand->_co = Red;

				cur = grand;
				parent = cur->_parent;
			}
			else
			{
				if(cur=)
			}

		}
		else
		{
			Node* uncle = grand->_left;
			if (uncle && uncle->_co == Red)
			{
				//这里只变色就好
				parent->_co = uncle->_co = Black;
				grand->_co = Red;

				cur = grand;
				parent = cur->_parent;
			}
		}
	}

 5.1.2 叔叔节点为黑或者不存在时,插入的节点为parent的外侧节点

    这种情况和以下的各种情况都不是单纯的插入引发的,而是通不断调整引发的。

例:
C++实现红黑树_第6张图片

 这种情况下我们只需要做一个简单的单旋就可以解决,因为这种情况的产生肯定是因为parent那条路径的插入所导致的(看上一种情况引发的变色),所以我们可以近似成单纯的左右旋转两种情况:

  ​​​​​​

C++实现红黑树_第7张图片

  这玩意是不是非常眼熟,其形式和我们前期学的旋转样例一模一样,所以我们只剩下了一个问题,变色。

   C++实现红黑树_第8张图片

经过旋转后,大概变成了这个形状,因此我们可以发现,我们不仅每条路径的黑色节点数目不一样,并且还出现了连续的红色节点。

  在此,为了维护红黑树的各种特点,我们将p变为黑色,将g变为红色,如下图表示

 C++实现红黑树_第9张图片

因此C节点下面是必定有节点,并且为黑色,所以这样变色就不会违背红黑树的各种特性,因此,我们可以得出如下代码。 

if (parent == grand->_left)
{
				Node* uncle = grand->_right;
				if (uncle && uncle->_co == Red)
				{
					//这里只变色就好
					parent->_co = uncle->_co = Black;
					grand->_co = Red;

					cur = grand;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)
					{
						_RotatoR(grand);

						grand->_co = Red;
						parent->_co = Black;
					}
					else
					{

					}
					break;
				}
}
else
{
				Node* uncle = grand->_left;
				if (uncle && uncle->_co == Red)
				{
					//这里只变色就好
					parent->_co = uncle->_co = Black;
					grand->_co = Red;

					cur = grand;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						_RotatoL(grand);

						grand->_co = Red;
						parent->_co = Black;
					}
					else
					{

					}
					break;
				}
}

5.1.3 叔叔节点为黑或者不存在,插入的节点为parent的内测节点

  这种情况下,最直接的引发情况就是,parent节点和cur节点都为红色。

如下图:
C++实现红黑树_第10张图片

这种情况下,我们可以近似于两种双旋:
C++实现红黑树_第11张图片

 C++实现红黑树_第12张图片

 具体图得:

C++实现红黑树_第13张图片C++实现红黑树_第14张图片

因此,我们可得出这种情况下的完整代码:
 

bool Insert(const pair& data)
{
	if (!_root)
	{
		_root = new Node(data);
		_root->_co = Black;
		return true;
	}
	
	Node* cur = _root;
	Node* parent = nullptr;

	while (cur)
	{
		if (cur->_data.first < data.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_data.first>data.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{ 
			return false;
		}
	}

	cur = new Node(data);
	cur->_co = Red;
	if (parent->_data.first < cur->_data.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;

	//开始判断是否需要变色
	while (parent && parent->_co == Red)
	{
		Node* grand = parent->_parent;
		if (parent == grand->_left)
		{
			Node* uncle = grand->_right;
			if (uncle && uncle->_co == Red)
			{
				//这里只变色就好
				parent->_co = uncle->_co = Black;
				grand->_co = Red;

				cur = grand;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_left)
				{
					_RotateR(grand);

					grand->_co = Red;
					parent->_co = Black;
				}
				else
				{
					_RotateL(parent);
					_RotateR(grand);

					cur->_co = Black;
					grand->_co = Red;
				}
				break;
			}
		}
		else
		{
			Node* uncle = grand->_left;
			if (uncle && uncle->_co == Red)
			{
				//这里只变色就好
				parent->_co = uncle->_co = Black;
				grand->_co = Red;

				cur = grand;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_right)
				{
					_RotateL(grand);

					grand->_co = Red;
					parent->_co = Black;
				}
				else
				{
					_RotateR(parent);
					_RotateL(grand);
					cur->_co = Black;
					grand->_co = Red;
				}
				break;
			}
		}
	}
	_root->_co = Black;
	return true;
}

六.红黑树的验证

  红黑树的验证可以从两部分入手,一部分是其本身二叉搜索树的性质,也就是中序遍历出来是一个有序的数组,第二部分是其红黑树本身的五个性质。

第一部分很简单,代码如下:
 

	void Inorder()
   {
		_Inorder(_root);
	}
private:

	void _Inorder(Node* root)
	{
		if (!root)
		{
			return;
		}

		_Inorder(root->_left);
		cout << root->_data.first <<":" << root->_data.second << endl;
		_Inorder(root->_right);
	}

第二部分,我们需要逐个排查红黑树的五条性质,在这一部分,我们建议分俩个函数来写,在第一个函数IsBalance中,我们需要排查头结点的颜色,并且我们需要统计出一条路径上的黑色节点数目,我们将黑色节点的数目传入第二个函数Check,让他来排查每一条路径上有没有连续的红色节点,还有每条路径的黑色节点数目是否等于isbalance给出的黑色节点数目。

  代码如下:
 

	bool IsBalance()
	{
		return _IsBalance();
	}
private:
	bool _IsBalance()
	{
		if (!_root) return true;
		if (_root->_co == Red)
		{
			cout << "根节点为红色" << endl;
			return false;
		}

		int BlackSum = 0; 
		Node* cur = _root;
		while (cur)
		{
			if (cur->_co == Black) BlackSum++;
			cur = cur->_left;
		}
		return _Check(_root, 0, BlackSum);
		}

	bool _Check(Node* root, int Blacknum, int BlackSum)
	{
		if (!root )
		{
			if (Blacknum != BlackSum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}
			return true;
		}
		if (root->_co == Black)
		{
			++Blacknum;
		}
		if (root->_co == Red&& root->_parent&& root->_parent->_co ==Red)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}
		return _Check(root->_left, Blacknum, BlackSum)&& _Check(root->_right, Blacknum, BlackSum);
	}

七.红黑树的查找

   红黑树的查找与二叉搜索树的查找一样,逻辑如下:

  1. 若树为空树,则查找失败,返回nullptr。
  2. 若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。
  3. 若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。
  4. 若key值等于当前结点的值,则查找成功,返回对应结点。

Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_data.first < key)
		{
			cur = cur->_right;
		}
		else if (cur->_data.first > key)
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}
	return nullptr;
}

完整代码如下: 

#pragma once
#include

using namespace std;

namespace My 
{
	enum Color
	{
		Red,
		Black
	};

	//直接实现kv模型的红黑树
	template
	class RBTreeNode
	{
	public:
		RBTreeNode(const pair& data)
			:_co(Red)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _data(data)
		{}
		Color _co;
		RBTreeNode* _left;
		RBTreeNode* _right;
		RBTreeNode* _parent;
		pair _data;
	};
	 
	template
	class RBTree
	{
	public:
		typedef RBTreeNode Node;
		
		bool Insert(const pair& data)
		{
			if (!_root)
			{
				_root = new Node(data);
				_root->_co = Black;
				return true;
			}
			
			Node* cur = _root;
			Node* parent = nullptr;

			while (cur)
			{
				if (cur->_data.first < data.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_data.first>data.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{ 
					return false;
				}
			}

			cur = new Node(data);
			cur->_co = Red;
			if (parent->_data.first < cur->_data.first)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			cur->_parent = parent;

			//开始判断是否需要变色
			while (parent && parent->_co == Red)
			{
				Node* grand = parent->_parent;
				if (parent == grand->_left)
				{
					Node* uncle = grand->_right;
					if (uncle && uncle->_co == Red)
					{
						//这里只变色就好
						parent->_co = uncle->_co = Black;
						grand->_co = Red;

						cur = grand;
						parent = cur->_parent;
					}
					else
					{
						if (cur == parent->_left)
						{
							_RotateR(grand);

							grand->_co = Red;
							parent->_co = Black;
						}
						else
						{
							_RotateL(parent);
							_RotateR(grand);

							cur->_co = Black;
							grand->_co = Red;
						}
						break;
					}
				}
				else
				{
					Node* uncle = grand->_left;
					if (uncle && uncle->_co == Red)
					{
						//这里只变色就好
						parent->_co = uncle->_co = Black;
						grand->_co = Red;

						cur = grand;
						parent = cur->_parent;
					}
					else
					{
						if (cur == parent->_right)
						{
							_RotateL(grand);

							grand->_co = Red;
							parent->_co = Black;
						}
						else
						{
							_RotateR(parent);
							_RotateL(grand);
							cur->_co = Black;
							grand->_co = Red;
						}
						break;
					}
				}
			}
			_root->_co = Black;
			return true;
		}

		void Inorder()
		{
			_Inorder(_root);
		}

		bool IsBalance()
		{
			return _IsBalance();
		}

		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_data.first < key)
				{
					cur = cur->_right;
				}
				else if (cur->_data.first > key)
				{
					cur = cur->_left;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}


	private:


		bool _IsBalance()
		{
			if (!_root) return true;
			if (_root->_co == Red)
			{
				cout << "根节点为红色" << endl;
				return false;
			}

			int BlackSum = 0; 
			Node* cur = _root;
			while (cur)
			{
				if (cur->_co == Black) BlackSum++;
				cur = cur->_left;
			}
			return _Check(_root, 0, BlackSum);
 		}

		bool _Check(Node* root, int Blacknum, int BlackSum)
		{
			if (!root )
			{
				if (Blacknum != BlackSum)
				{
					cout << "某条路径黑色节点的数量不相等" << endl;
					return false;
				}
				return true;
			}
			if (root->_co == Black)
			{
				++Blacknum;
			}
			if (root->_co == Red&& root->_parent&& root->_parent->_co ==Red)
			{
				cout << "存在连续的红色节点" << endl;
				return false;
			}
			return _Check(root->_left, Blacknum, BlackSum)&& _Check(root->_right, Blacknum, BlackSum);
		}


		void _Inorder(Node* root)
		{
			if (!root)
			{
				return;
			}

			_Inorder(root->_left);
			cout << root->_data.first <<":" << root->_data.second << endl;
			_Inorder(root->_right);
		}

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

			parent->_left = SubLR;
			if (SubLR) SubLR->_parent = parent;

			Node* pparent = parent->_parent;

			SubL->_right = parent;
			parent->_parent = SubL;

			if (_root==parent)
			{
				_root = SubL;
				SubL->_parent = nullptr;
			}
			else
			{
				if (pparent->_left == parent)
				{
					pparent->_left = SubL;
				}
				else
				{
					pparent->_right = SubL;
				}
				SubL->_parent = pparent;
			}
		}

		void _RotateL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			parent->_right = subRL;
			subR->_left = parent; //将subR的左指针指向parent

			Node* pparent = parent->_parent;

			parent->_parent = subR;//将parent的父指针指向subR

			if (subRL)
				subRL->_parent = parent;

			if (_root == parent) //判断parent是否是头节点
			{
				_root = subR;
				subR->_parent = nullptr;
			}
			else
			{
				if (pparent->_left == parent)
				{
					pparent->_left = subR;
				}
				else
				{
					pparent->_right = subR;
				}
				subR->_parent = pparent;
			}
		}

		Node* _root = nullptr;
	};
}

你可能感兴趣的:(java,开发语言,c++,数据结构,青少年编程)