红黑树的实现

红黑树的引入

有了二叉搜索树,为什么还需要平衡二叉树?

  • 假设往树中插入的元素有序或者接近有序,二叉搜索树就会退化成为单支树。
  • 二叉搜索树就会很容易退化成一条链。
  • 这时,查找的时间复杂度从O\left ( log_2{N} \right )也退化成为O\left ( N \right )
  • 引入对左右子树高度差有限制的平衡二叉树,保证查找操作的最坏时间复杂度也为O\left ( log_2{N} \right )

有了平衡二叉树,为什么还需要红黑树?

  • AVL(自平衡的二叉搜索树)的左右子树高度差不能超过1,每次进行插入、删除操作时,几乎都需要通过旋转操作保持平衡;
  • 在频繁进行插入、删除的场景中,频繁的旋转操作使得AVL的性能也是大打折扣;
  • 红黑树通过牺牲严格的平衡,换取插入、删除时少量的旋转操作,整体性能优于AVL;
  • 红黑树插入时的不平衡,不超过两次旋转就可以解决;删除时的不平衡,不超过三次旋转就能解决;
  • 红黑树的红黑规则,保证在最坏的情况下,也能在O\left ( log_2{N} \right )时间内完成查找操作。

红黑树的概念

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

红黑树的性质

首先,红黑树是一个二叉搜索树,它在每个节点增加了一个存储位记录节点的颜色,可以是RED,也可以是BLACK。

通过任意一条从根到叶子简单路径上颜色的约束:

红黑树保证了最长路径不超过最短路径的二倍,因而近似平衡。

  • 最短路径就是全黑节点;
  • 最长路径就是一个红节点一个黑节点;
  • 当从根节点到叶子节点的路径上黑色节点相同时,最长路径刚好是最短路径的两倍。

【重点】红黑树的性质:

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

红黑树节点的定义

 

//枚举红黑树颜色
enum Color
{
	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;
	Color _col;

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

红黑树的插入操作

插入时,应该插入的是红色节点

  • 如果插入的是黑色节点,那么必然会违反规则。
  • 因为每一条的路径中的黑色节点数量相同。
  • 插入红色节点,可能会违反规则,可能会出现连续的红色节点。

红黑树是二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  • 按照二叉搜索树规则插入新节点;
  • 检验新节点插入后,红黑树的性质是否遭到破坏。

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

  • Node* parent = nullptr;
    Node* cur = _root;
    if (_root == nullptr)
    {
    	_root = new Node(kv);
    	_root->_col = BLACK;
    	return true;
    }
    while (cur)
    {
    	if (cur->_kv.first > kv.first)
    	{
    		parent = cur;
    		cur = cur->_left;
    	}
    	else if (cur->_kv.first < kv.first)
    	{
    		parent = cur;
    		cur = cur->_right;
    	}
    	else
    	{
    		return false;
    	}
    }
    cur = new Node(kv);
    cur->_col = RED;
    if (parent->_kv.first > kv.first)
    {
    	parent->_left = cur;
    }
    else
    {
    	parent->_right = cur;
    }
    cur->_parent = parent;

检验新节点插入后,红黑树的性质是否遭到破坏

如果插入红色之后,父亲是黑色节点,满足红黑树的性质。

Cur为当前节点,p为父亲节点,g为祖父节点,u为叔叔节点。

默认此时插入的cur红色节点的父亲节点为红色,那么此时祖父节点肯定是黑色的,并且cur的叔叔节点不能确定是否为什么色。

所以主要以u叔叔节点来分类讨论:

  • Cur红,p红,g黑,u红

  • Cur红,p红,g黑,u黑或者不存在

Cur红,p红,g黑,u红

解决方法:将父亲、叔叔变为黑,将祖父g变为红色。

其中祖父不能不变为红,有可能g是整棵树的子树。

g不变红,就会p与u无缘无故变黑,黑节点多了一个,这棵树的路径黑色节点的数量+1,破坏了红黑树的规则,所以需要向上调整。

Cur红,p红,g黑,u黑或者不存在

 

//进行调整
while (parent && parent->_col == RED)
	//调整中如果父亲是黑色节点或者为空,就不需要继续向上调整
{
	Node* grandfather = parent->_parent;
	if (grandfather->_left == parent)
	{
		Node* uncle = grandfather->_right;
		//cur、p、g为红色,u存在且为红【不会影响左右子树的黑色数量,只需要变色】

		if (uncle && uncle->_col == RED)
		{
			parent->_col = BLACK;
			uncle->_col = BLACK;
			cur = grandfather;
			parent = cur->_parent;
		}
		else
			//u不存在或者为黑色,需要进行调整使得左右子树的黑色子树数量一致
		{
			if (cur == parent->_left)
			{
				RotateR(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			else
			{
				RotateL(parent);
				RotateR(grandfather);
				cur->_col = BLACK;
				grandfather->_col = RED;
			}
			break;
		}
	}

	else
	{
		Node* uncle = grandfather->_left;
		//cur、p、g为红色,u存在且为红【不会影响左右子树的黑色数量,只需要变色】

		if (uncle && uncle->_col == RED)
		{
			parent->_col = BLACK;
			uncle->_col = BLACK;
			cur = grandfather;
			parent = cur->_parent;
		}
		else
			//u不存在或者为黑色,需要进行调整使得左右子树的黑色子树数量一致
		{
			if (cur == parent->_right)
			{
				RotateL(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			else
			{
				RotateR(parent);
				RotateL(grandfather);
				cur->_col = BLACK;
				grandfather->_col = RED;
			}
			break;
		}
	}
}

红黑树的验证

红黑树的检测分为两步:

  • 检测其是否满足二叉搜索树中序遍历是否为有序序列)
  • 检测其是否满足红黑树的性质
	//检查此树是否满足红黑树的性质
	bool IsBalance()
	{
		//①:如果此树的根结点为红色,直接返回错误
		//②:如果此树的每条路径的黑色节点的数量不同,则就是返回flase
		if (_root->_color = RED)
		{
			return true;
		}
		else
		{
			//找一条路径为判断每条路径的黑色节点数为一个基准
			int refNum = 0;
			Node* cur = _root;
			while (cur)
			{
				//用最左边的路径为基准
				if (cur->_color == BLACK)
				{
					refNum++;
				}
				cur = cur->_left;
			}
			return Check(_root, 0, refNum);
		}
	}
private:
	bool Check(Node* root, int blackNum, const int refNum)
	{
		//此处需要的是递归
		//直到遍历到了空节点,判断是否计算出来的refNum与blackNum是相同的,如果不相同就是返回false
		if (root == nullptr)
		{
			if(refNum!=blackNum)
			{
				cout << "存在cout存在黑色节点的数量不相等的路径 " << endl;
				return false;
			}
			//如果又连续的红色节点就返回错误
			if (root->_color == RED && root->_parent->_color == RED)
			{
				cout << root->_kv.first << "存在连续的红色节点" << endl;
				return false;
			}
			//如果遇到黑色节点就++
			if (root->_color == BLACK)
			{
				blackNum++;
			}

			return Check(root->_left, blackNum, refNum) &&
				Check(root->_right, blackNum, refNum);
		}
	}

红黑树的完整代码

#pragma once
#include <vector>
//红黑树的定义
//定义红黑树的节点

enum Color
{
	RED,
	BLACK
};

template<class K,class V>
struct RBTreeNode
{
	pair<K, V> _kv;//节点中存储的值
	RBTreeNode<K, V>* _left;//节点的左孩子
	RBTreeNode<K, V>* _right;//节点的右孩子
	RBTreeNode<K, V>* _parent;//节点的父亲
	Color _color;//节点的颜色

	RBTreeNode(const pair<K,V>& kv)
		:_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_kv(kv),
		_color(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->_color = BLACK;
			return true;
			//根结点必须是黑色的
		}
		//如果此红黑树不是空的,就找到相应的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		//新增的节点的颜色为红色
		cur->_color = RED;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//插入节点结束
		
		//进行调整
		//检查红黑树的性质是否遭到了破坏:
			//因为插入的节点是红色的;
			//根据红黑树的特性可以:不能出现连续的红色节点
			//所以如果插入节点之后的新节点的父亲是黑色的,那么就说明是不需要调整的此时是正确的

		while (parent && parent->_color == RED)//所以如果父亲节点是红色的,就需要进行调整了
		{
			//因为红黑树的另一个特性:
				//每条路径的黑色节点的个数是一致的(所以在进行调整的时候,需要考虑左右子树的黑色节点)
			//插入新节点之后,有以下两种情况:
			// 【影响or不影响“每条路径的黑色节点的数量一致”的性质】
				//①cur为红,p为红,g为黑,u存在且为红【不影响“每条路径的黑色节点的数量一致”】
				//②cur为红,p为红,g为黑,u不存在或为黑【影响“每条路径的黑色节点的数量一致”】
			//【注】:以上这两种情况中,只有u节点是变量
				//因为在插入每一个节点之后都得保证此树仍然是红黑树的特性。
				//所以在插入每一个新的节点之后,都会重新进行调整为一个合格的红黑树。
				//所以在插入的时候,每插入的一个新的结点之前都是一个合格的红黑树

			//关键看叔叔
				//u存在且为红:此时因为p一定为红色,所以p和u都是红色,因为p和u上面是满足红黑树的
				//所以此时不会影响“每一条路径上的黑色节点的数量一致”的性质
				//此时,只需要变色,不用考虑每条路径的黑色节点的数量
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_color == RED)//u存在且为红
				{
					//g:子树or根节点
						//g为子树:g一定有双亲,且g的双亲如果是红色,需要继续向上调整
						//g为根,调整完成之后,需要将g改为黑色
					
					parent->_color = BLACK;
					uncle->_color = BLACK;

					cur = grandfather;
					parent = cur->_parent;
					
				}
				else//u不存在或者为黑色
				{

					//如果u节点不存在,则cur一定是新插入节点,
					//因为如果cur不是新插入的节点,那么新插入的节点就在cur的下面,那么此时因为“不能出现连续的红色节点”,
					//所以p或者cur之间一定有一个原本是黑色的,
					//但是此时又会不满足“每条路径的黑色节点的数量一致”

					//如果u节点存在且为黑色,那么cur节点原来的颜色一定是黑色的,
					//现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色变为红色了

					//u不存在或者为黑色【旋转】
					if (cur == parent->_left)//右单旋
					{
						RotateR(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					else//左右双旋
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}

					break;
				}
			}

			else
			{
				Node* uncle = grandfather->_left;
				if (uncle&& uncle->_color == RED)//u存在且为红
				{
					//g:子树or根节点
						//g为子树:g一定有双亲,且g的双亲如果是红色,需要继续向上调整
						//g为根,调整完成之后,需要将g改为黑色
					
					parent->_color = BLACK;
					uncle->_color = BLACK;

					cur = grandfather;
					parent = cur->_parent;
					
				}
				else//u不存在或者为黑色
				{

					//如果u节点不存在,则cur一定是新插入节点,
					//因为如果cur不是新插入的节点,那么新插入的节点就在cur的下面,那么此时因为“不能出现连续的红色节点”,
					//所以p或者cur之间一定有一个原本是黑色的,
					//但是此时又会不满足“每条路径的黑色节点的数量一致”

					//如果u节点存在且为黑色,那么cur节点原来的颜色一定是黑色的,
					//现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色变为红色了

					//u不存在或者为黑色【旋转】
					if (cur == parent->_right)//右单旋
					{
						RotateL(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					else//左右双旋
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
		}
		_root->_color = BLACK;
		return true;
	}

	//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		subL->_right = parent;
		Node* ppNode = parent->_parent;
		parent->_parent = subL;
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = 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;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}

	}
	void InOrder()
	{
		_InOrder(_root);
		return;
	}

	//检查此树是否满足红黑树的性质
	bool IsBalance()
	{
		//①:如果此树的根结点为红色,直接返回错误
		//②:如果此树的每条路径的黑色节点的数量不同,则就是返回flase
		if (_root->_color = RED)
		{
			return true;
		}
		else
		{
			//找一条路径为判断每条路径的黑色节点数为一个基准
			int refNum = 0;
			Node* cur = _root;
			while (cur)
			{
				//用最左边的路径为基准
				if (cur->_color == BLACK)
				{
					refNum++;
				}
				cur = cur->_left;
			}
			return Check(_root, 0, refNum);
		}
	}
private:
	bool Check(Node* root, int blackNum, const int refNum)
	{
		//此处需要的是递归
		//直到遍历到了空节点,判断是否计算出来的refNum与blackNum是相同的,如果不相同就是返回fals
		if (root == nullptr)
		{
			if(refNum!=blackNum)
			{
				cout << "存在cout存在黑色节点的数量不相等的路径 " << endl;
				return false;
			}
			//如果又连续的红色节点就返回错误
			if (root->_color == RED && root->_parent->_color == RED)
			{
				cout << root->_kv.first << "存在连续的红色节点" << endl;
				return false;
			}
			//如果遇到黑色节点就++
			if (root->_color == BLACK)
			{
				blackNum++;
			}

			return Check(root->_left, blackNum, refNum) &&
				Check(root->_right, blackNum, refNum);
		}
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		else
		{
			_InOrder(root->_left);
			cout << root->_kv.first << ":" << root->_kv.second << endl;
			_InOrder(root->_right);
		}
	}
private:
	Node* _root = nullptr;
};

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

		t1.Insert(make_pair(e, e));

		// 1、先看是插入谁导致出现的问题
		// 2、打条件断点,画出插入前的树
		// 3、单步跟踪,对比图一一分析细节原因

		cout << "Insert:" << e << "->" << e << endl;
	}

	t1.InOrder();

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

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

		// 1、先看是插入谁导致出现的问题
		// 2、打条件断点,画出插入前的树
		// 3、单步跟踪,对比图一一分析细节原因
		t1.Insert({ e,e });

		//cout << "Insert:" << e << "->" << t1.IsBalance() << endl;
	}

	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);
		//cout << v.back() << endl;
	}

	size_t begin2 = clock();
	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
		//cout << "Insert:" << e << "->" << t.IsBalance() << endl;
	}
	size_t end2 = clock();

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

红黑树与AVL树的比较

  • 红黑树和AVL树都是高效的平衡二叉树;增删查改的时间复杂度都是O\left ( log_2{N} \right )
  • 红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数;
  • 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安心学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值