红黑树【C++】

目录

[1 红黑树定义](#1 红黑树定义)

[2 红黑树的的性质](#2 红黑树的的性质)

[3 红黑树的插入](#3 红黑树的插入)

[4 红黑树检查](#4 红黑树检查)

[5 完整源码实现](#5 完整源码实现)

1 红黑树定义

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

红黑树节点定义

cpp 复制代码
enum Colour
{
	//使用枚举的原因是方便调试,宏不能调试
	RED,
	BLACK
};

template<class K,class V>
struct RBTreeNode 
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K,V>& kv) 
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_col(RED)
	{}

};

2 红黑树的的性质

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

  2. 根结点是黑色的

  3. 如果一个结点是红色的,则它两个孩子结点是黑色的(不存在连续的红色结点

  4. 对于每个结点,从该结点到其后代叶子结点的简单路径上,均包含相同数目的黑色结点(每条路径都存在相同数量的黑色结点)

  5. 每个叶子结点都是都是黑色的(这里的叶子结点指的是空结点)

红黑树的条件/颜色限制:最长路径 不超过 最短路径的二倍

3 红黑树的插入

当树为空直接插入新界点即可

cpp 复制代码
		//红黑树插入逻辑
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

当不为空就先按照搜索树的规则进行插入,随后再进行调整

cpp 复制代码
		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 = new Node(kv);
		cur->_col = RED;	//新增节点给红色
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

注意:

对于新插入节点,插入红色(可能违反性质3,出现连续的红色节点);插入黑色,一定违反红黑树的性质4;所以一般情况下新插入的节点是红色。

分情况进行处理:

1 当前节点为红色,父亲节点为红色,祖父节点为黑色,叔叔节点为红色

cur为红,p为红,g为黑,u存在且为红

这里的a/b/c/d/e指的是每条路径有 x个黑色节点的红黑树子树(x >= 0)

x == 0 , cur节点为新增节点

解决方法: 将 p,u 变成 黑色,g变成红色,然后把g当成cur, 继续向上调整:

需要注意的是 g节点不能变成黑色,因为g所在的树可能是子树,如果g节点变成黑色的话,这样就会违反性质四(每条路径上都存在相同数量的黑色节点)

但是调整仍会出现下面的情况:

① 当祖父g节点是根节点时,需要再次把根节点变成黑色

② 当祖父g节点不是根节点,继续往上调整:

1)如果g节点的父亲节点是黑色那就直接结束

2)如果g节点的父亲节点是红色的,还要继续处理 g 变成 cur ,然后将 p,u 变成 黑色,g变成红色,然后把g当成cur, 继续向上调整;

调整方法如图:

cpp 复制代码
		//parent的颜色是黑色就结束
		while (parent && parent->_col == RED)
		{
			//当父亲的颜色是红色,还要去找祖父节点
			Node* grandfather = parent->_parent;

			//这个时候就要分情况

			//情况 1
			//关键看叔叔节点,如果父亲节点在祖父节点的左边,叔叔节点就在祖父节点的右边
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//叔叔存在且为红,变色即可
				//把叔叔和父亲节点变成黑色,祖父节点变成红色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;


					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}

x == 1 ,也就是有一个黑色节点的红黑树子树

对于c/d/e 这三个分支 是m/n/p/q中 任意一个,组合:4*4*4 = 64种情况

新增节点插入位置是a和b的孩子的时候,4个位置,组合:256种情况

对于该情况,插入新增的红色节点,就需要先进行变色处理(把父亲和叔叔节点变成黑色节点,祖父节点变成红色节点,然后继续向上处理)。

2 当前节点为红色,父亲节点为红色,祖父节点为黑色,叔叔节点不存在 或者 叔叔节点存在且为黑色

cur 为红色,p为红,g为黑,u不存在 或者 u存在且为黑

① 叔叔u节点不存在

因为cur和p都是红色,所以在不违反性质4的情况下,p变黑,g变红,然后把该子树进行右单旋转。

② 叔叔u节点存在且为黑色

这里的①与②均是单旋就可以处理,还要考虑到双旋的问题:

分析:首先这种情况的下普通的单旋转是解决不了的(需要通过双旋来解决)这里uncle节点为空

cpp 复制代码
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//叔叔存在且为红,变色即可
				//把叔叔和父亲节点变成黑色,祖父节点变成红色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;


					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 叔叔不存在 或者存在且为黑色,父节点变为黑色,祖父节点变成红色,然后旋转
				{

					//这里还需要注意旋转是单旋 还是 双旋
					if (cur == parent->_left)
					{
						//		g
						//	p		u
						//c
						//当前节点是父节点左边的节点则进行右单旋
						RotateR(grandfather);	//对爷爷节点作为旋转点进行右单旋
						//旋转完后,把父节点变成黑色,爷爷节点变成红色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//		 g
						//	p		 u
						//	  c
						//当前节点是父节点的右边的节点则进行双旋,以parent为旋转点进行左单旋,
						//然后在进行以grandfather为旋转点进行一个右单旋,cur的右边给g节点的左边
						//旋转完以后,把cur变成根变成黑色,把g节点变成红色
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}

小总结:

对于红黑树插入新节点: 关键看叔叔节点是否存在

a) 当叔叔节点存在且为红色,进行变色处理,父亲节点和叔叔节点均变成黑色,祖父节点变成红色,然后继续向上处理。

b) 当叔叔节点存在 或者 存在且为黑色,先判断需要进行单旋处理还是双旋处理,然后再进行变色处理。

最后保证根节点的颜色必须是黑色的。

4 红黑树检查

1 判断根节点是否是黑色的

cpp 复制代码
		//1 检查根节点是否是黑色的
		if (_root->_col == RED) 
		{
			return false;
		}
		return Check(_root,0);

2 判断是否存在连续的红色节点

如果当前为空,返回true

如果当前节点和其父节点都是红色则存在连续红色节点

cpp 复制代码
		if (root ==nullptr)
		{
			return true;
		}
		//2 判断是否存在连续红色节点
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续红色节点" << endl;
			return false;
		}

3 判断某节点到叶子节点上的所有简单路径上的黑色节点数量是否相同

每个节点记录一个值:从根到当前节点路径上的黑色节点的数量:使用递归形参来记录(递归展开图)

同时使用参考值进行对比,这里的参考值指的是从根节点到任意一条路径上的黑色节点的数量。

4 继续递归左右子树

部分递归展开图:

cpp 复制代码
	bool Check(Node* root,int blackNum,int refNum) 
	{
		if (root ==nullptr)
		{
			
			//判断当前黑色节点的个数是否与参考的黑色节点的个数相等
			if (refNum != blackNum)
			{
				cout << "存在黑节点数量不相等的路径" << endl;
				return false;
			}
			return true;
		}
		//2 判断是否存在连续红色节点
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续红色节点" << endl;
			return false;
		}
		//3 计算黑色节点的个数
		if (root->_col == BLACK)
		{
			++blackNum;
		}
		//4 继续递归左右子树
		return Check(root->_left,blackNum,refNum) && Check(root->_right,blackNum,refNum);
	}

5 完整源码实现

cpp 复制代码
#pragma once
#include<iostream>
#include<vector>
using namespace std;

enum Colour
{
	//使用枚举的原因是方便调试,宏不能调试
	RED,
	BLACK
};

template<class K,class V>
struct RBTreeNode 
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K,V>& kv) 
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_col(RED)
	{}

};


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);
			_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 = new Node(kv);
		cur->_col = RED;	//新增节点给红色
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//parent的颜色是黑色就结束
		while (parent && parent->_col == RED)
		{
			//当父亲的颜色是红色,还要去找祖父节点
			Node* grandfather = parent->_parent;

			//这个时候就要分情况

			//情况 1
			//关键看叔叔节点,如果父亲节点在祖父节点的左边,叔叔节点就在祖父节点的右边
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//叔叔存在且为红,变色即可
				//把叔叔和父亲节点变成黑色,祖父节点变成红色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;


					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 叔叔不存在 或者存在且为黑色,父节点变为黑色,祖父节点变成红色,然后旋转
				{

					//这里还需要注意旋转是单旋 还是 双旋
					if (cur == parent->_left)
					{
						//		g
						//	p		u
						//c
						//当前节点是父节点左边的节点则进行右单旋
						RotateR(grandfather);	//对爷爷节点作为旋转点进行右单旋
						//旋转完后,把父节点变成黑色,爷爷节点变成红色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//		 g
						//	p		 u
						//	  c
						//当前节点是父节点的右边的节点则进行双旋,以parent为旋转点进行左单旋,
						//然后在进行以grandfather为旋转点进行一个右单旋,cur的右边给g节点的左边
						//旋转完以后,把cur变成根变成黑色,把g节点变成红色
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else  //情况 2
			{
				//否则叔叔节点就在祖父节点的左边
				Node* uncle = grandfather->_left;
				//叔叔存在且为红,变色即可
				//把叔叔和父亲节点变成黑色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else //叔叔不存,或者存在且为黑色
				{
					if (cur == parent->_right)
					{
						//		g
						//	u		p
						//				c
						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;
	}

	void RotateR(Node* parent)
	{
		//因为左边高,需要右旋进行调整
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//修改指向,方便后续的旋转
		parent->_left = subLR;

		//subLR是否为空
		if (subLR) 
		{
			subLR->_parent = parent;
		}

		Node* ppNode = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;

		//因为这里的不平衡点是parent,当parent是根节点时
		//修改旋转的新根节点,新根节点的父节点直接置空即可
		if (parent == _root) 
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			//旋转后,parent节点不是根节点时,修正原parent节点的父节点的指向
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else 
			{
				ppNode->_right = subL;
			}

			//更新subL的父指针的指向
			subL->_parent = ppNode;
		}


	}

	void RotateL(Node* parent)
	{
		//因为右边高,需要进行左旋调整平衡
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//旋转调整
		parent->_right = subRL;

		if (subRL) 
		{
			//因为是三叉链表,除了左右指针还要进行修改指向父亲节点的指针
			subRL->_parent = parent;
		}

		subR->_left = parent;

		Node* ppNode = parent->_parent;

		parent->_parent = subR;

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

	void InOrder() 
	{
		_InOrder(_root);
	}

	bool IsBalance() 
	{
		//1 检查根节点是否是黑色的
		if (_root->_col == RED) 
		{
			return false;
		}

		//选取任意从根到当前节点的路径,计算该路径上的黑色节点的数量(作为参考值)
		Node* cur = _root;
		int refNum = 0;
		while (cur) 
		{
			if (cur->_col == BLACK) 
			{
				++refNum;
			}
			cur = cur->_left;	//这里选取红黑树最左边的路径上的黑色节点的个数作为参考值
		}
		return Check(_root,0,refNum);
	}



private:


	bool Check(Node* root,int blackNum,int refNum) 
	{
		if (root ==nullptr)
		{
			
			//判断当前黑色节点的个数是否与参考的黑色节点的个数相等
			if (refNum != blackNum)
			{
				cout << "存在黑节点数量不相等的路径" << endl;
				return false;
			}
			return true;
		}
		//2 判断是否存在连续红色节点
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续红色节点" << endl;
			return false;
		}
		//3 计算黑色节点的个数
		if (root->_col == BLACK)
		{
			++blackNum;
		}
		//4 继续递归左右子树
		return Check(root->_left,blackNum,refNum) && Check(root->_right,blackNum,refNum);
	}


	void _InOrder(Node* root) 
	{
		//中序遍历
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << " " << root->_kv.second << endl;
		_InOrder(root->_right);
	}



	Node* _root = nullptr;
	//size_t _size = 0;
};


void TestRBTree1() 
{
	//int a[] = {8,3,1,10,6,4,7,14,13};
	int a[] = {4,2,6,1,3,5,15,8,3,1,10,6,4,7,14,13 };
	RBTree<int, int> t1;
	for (auto e : a) 
	{
		t1.Insert({e,e});
	}

	t1.InOrder();

	cout << t1.IsBalance() << endl;
}

void TestRBTree2() 
{
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);

	srand(time(0));

	for (size_t i = 0; i < N;i++)
	{
		v.push_back(rand()+i);
	}

	RBTree<int, int> t;

	for (auto e : v)
	{
		t.Insert({e,e});
	}

	cout << t.IsBalance() << endl;

}