红黑树的概念以及基本模拟

目录

一、概念和规则:

1、思考为什么最长路径不超过最短路径的二倍?

2、红黑树的效率?

二、红黑树的代码实现

1、红黑树的节点结构

2、红黑树的插入

1、大致过程:

2、维护的三种情况:

1、情况一:变色

2、情况二:单旋+变色

 3、情况三:双旋+变色

3、红黑树的验证

4、整体代码


一、概念和规则:

  • 红黑树是一颗搜索二叉树,满足左小右大(或者左小右大)的规则;(前提规则)
  • 红黑树每一个节点不是黑色就是红色;(规则一)
  • 根节点是黑色的;(规则二)
  • 不能出现连续的红色节点,若一个节点是红色的,那么它的两个孩子都是黑色的;(规则三)
  • 对于任何一条简单路径(从根到空),其上的黑色节点的数量都是相同的;(规则四)
  • 红黑树的最长路径不超过最短路径的二倍(规则推论)

1、思考为什么最长路径不超过最短路径的二倍?

由于每条路径的黑色节点的数量的个数相同,极端情况下最短路径的长度就是全是黑色节点的数量,最长路径长度就是红色黑色相间的路径,那么恰好就是最短路径的二倍;那么其他的路径长度都在最长与最短之间,那么最长路径就不会超过最短路径的二倍。

2、红黑树的效率?

假设红黑树最短路径的高度为H,那么最长路径的长度最长为 2*H ,其他的都在这两者之间,那么节点的数量N(根据等比求和)就在 2^H-1到2^(2*H)-1之间,那么红黑树增删查的时间复杂度还是O(logN),和AVL属于同一量级;

AVL是通过左右子树高度差来控制平衡;而红黑树是通过规则和颜色来达到近似平衡。

二、红黑树的代码实现

1、红黑树的节点结构

enum Color
{
	RED,
	BLACK
};

template<class K,class V>
struct RBTreeNode
{
	Color _col;
	pair<K, V> _kv;
	RBTreeNode<K, V>* _parent;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;

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

这里用枚举定义颜色,方便观察;

2、红黑树的插入

1、大致过程:

  • 先按照二叉搜索树进行插入;
  • 若是空树,就将新插入的节点认作根节点,并且把颜色赋为黑色;若不是空树,那么插入之后,这个新插入的节点必须赋为红色,否则会破坏所有路径黑色节点数量相同的规则;但是连续的两个红色也会破坏,但是连续的两个红色更好维护;
  • 插入红色的,若插入的父节点是黑色的,那么就是正常的,无需维护,插入完成直接退出;
  • 插入红色的,若插入的父节点是红色的,那就需要维护,因为此时破坏了规则;而维护有三种情况,维护的关键是看叔节点,也就是父亲的兄弟节点;

2、维护的三种情况:

1、情况一:变色

此种情况出现适用于:当uncle节点存在并且是红色时;

首先grandfather节点(parent和uncle的父节点)时黑色的,parent和uncle节点是红色的,这是确定的条件;此时给parent插入红色的新节点,要维护规则;进行变色:将grandfather变红,parent和uncle变黑;

一次变完之后,因为grandfather变红了,但是其上可能还有节点,当其父节点为红时,还需要继续调整,将cur赋值为grandfather,parent跟着向上调,uncle随之变化;直到父节点的颜色为黑或者父节点为根节点时结束循环;最后会单独处理根节点,无论根节是哪种颜色,都赋为黑;

2、情况二:单旋+变色

适用情形,uncle为空或者为黑时;此时只是单纯的变色解决不了问题,因为每条简单路径上面的黑色节点数量会不同;

当uncle不存在时,那么cur一定是新插入进来的,若不是新插入的那么cur下面还有黑色节点,那么parent的左右子树的黑色节点数量就不同;若uncle存在且为黑色,那么cur一定不是新插入的红色节点,而是cur下面的节点通过变色变上来的红色节点,否则parent的左右子树黑色节点的数量就不同,违反规则;

那么就要将grandfather作为旋转基准进行左单旋或者右单旋,旋转之后parent成了cur和grandfather的根节点,此时再把parent变黑,grandfather变红 ;无论是uncle存在与否,最后都能达到维护的目的,并且每条简单路径上面的黑色节点的数量都相同。

旋转并且变色之后这颗红黑树整体上就已经满足规则了,无需像情况一一样向上调整。

 3、情况三:双旋+变色

首先和情况二前提相同:若uncle不存在,则cur一定为新插入的;若uncle存在且为黑,则cur一定不是新插入的,而是调整上来的;

双旋的原因:不同于单旋,当parent是grandfather的左节点但是cur是father的右节点时,单旋不能解决问题,若是单旋,只是交换了左右位置,本质上还是没有完成规则的维护;此时需要双旋之后再变色;

第一次循环以parent为基准旋转,第二次以grandfather为基准旋转;最后将cur变为黑,grandfather变为红;

3、红黑树的验证

 红黑树的验证要严格按照四条规则来,验证每一条规则是否被满足,只有当所有的规则都被满足才说明这棵树是红黑树;

  1. 直接判断根节点是否是黑色
  2. 每个节点不是黑就是红,这个天然而成无需判断
  3. 检查是否有红色连续的情况,通过递归每次检查当前节点的父亲,当前节点为红色并且当前节点的父节点也是红色时,就返回false;检查父节点更方便;若是每次检查当前节点的左右子树节点相较麻烦一点
  4. 检查每条简单路径上面是否含有相同黑色节点数量时,先计算一条路径上面的黑色节点数量作为一个比较的基准值,再在递归的过程中计算每条路径的黑色节点数量,当递归的根为空时说明一条路径走完了,此时比较,若不想等就返回false;整体的返回是左右子树通过&&连接来返回,也就是有一条不满足的这棵树就不是一颗红黑树的意思

4、整体代码

#include<iostream>
using namespace std;


enum Color
{
	RED,
	BLACK
};

template<class K,class V>
struct RBTreeNode
{
	Color _col;
	pair<K, V> _kv;
	RBTreeNode<K, V>* _parent;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;

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

template<class K,class V>
class RBTree
{
	using Node = RBTreeNode<K, V>;
public:
	void Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return;
			}
		}
		cur = new Node(kv);
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
		cur->_col = RED;
		
		//parent为空或者 parent颜色为黑色,插入了满足条件,不用进循环
		//进循环说明 parent为红并且其父节点为黑,新插入的只能是红
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			//两个大方向:parent在 grandfather左边或者右边
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//首先是不用旋转的情况,此时无论 cur在 parent哪边都一样
				//将 grandfather变红,father和 uncle变黑
				//这种情况就是 uncle存在并且为红时
				if (uncle && uncle->_col == RED)
				{
						grandfather->_col = RED;
						parent->_col = uncle->_col = BLACK;
						cur = grandfather;
						parent = cur->_parent;
				}
				//这里的 else含义是:uncle为空或者不为空但是颜色是黑色
				//要旋转+变色
				else
				{
					//uncle若是为空,cur就必是新插入的节点,否则每一条路径的黑色节点数量不同
					//uncle若不为空,cur就必不是新插入的节点,这样每条路径的黑色节点数量才可能相同
					//无论为空为否,都要进行旋转再变色
					if (parent->_left == cur)
					{
						//右单旋
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
						break;//旋转完之后满足规则
					}
					else
					{
						//先左旋再右旋
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
				}
			}

			//parent为 grandfather的右孩子
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					grandfather->_col = RED;
					parent->_col = uncle->_col = BLACK;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (parent->_right == cur)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
				}

			}
		}
		//将根节点置为黑色
		_root->_col = BLACK;
	}
	Node* Find(const K& k)
	{
		Node* cur = _root;
		while (cur)
		{
			if (k < cur->_kv.first)
			{
				cur = cur->_left;
			}
			else if (k > cur->_kv.first)
			{
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
	}
	bool IsRBTree()
	{
		if (!_root)
			return true;
		if (_root->_col == RED)
			return false;

		int mode_count = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				mode_count++;
			cur = cur->_left;
		}

		return Check(_root, 0, mode_count);
	}
	
	void Print()
	{
		_Print(_root);
		cout << endl;
	}

private:
	Node* _root = nullptr;
	//右单旋
	void RotateR(Node* sub)
	{
		Node* subl = sub->_left;
		Node* sublr = subl->_right;

		sub->_left = sublr;
		if (sublr)
			sublr->_parent = sub;

		Node* sub_P = sub->_parent;
		subl->_right = sub;
		sub->_parent = subl;

		if (!sub_P)
		{
			_root = subl;
			subl->_parent = nullptr;
		}
		else
		{
			if (sub_P->_left == sub)
			{
				sub_P->_left = subl;
			}
			else
			{
				sub_P->_right = subl;
			}
			subl->_parent = sub_P;
		}
	}
	//左单旋
	void RotateL(Node* sub)
	{
		Node* subr = sub->_right;
		Node* subrl = subr->_left;

		sub->_right = subrl;
		if (subrl)
		{
			subrl->_parent = sub;
		}

		Node* sub_P = sub->_parent;
		subr->_left = sub;
		sub->_parent = subr;

		if (!sub_P)
		{
			_root = subr;
			subr->_parent = nullptr;
		}
		else
		{
			if (sub_P->_left == sub)
			{
				sub_P->_left = subr;
			}
			else
			{
				sub_P->_right = subr;
			}
			subr->_parent = sub_P;
		}
	}
	void _Print(Node* root)
	{
		if (!root)
		{
			return;
		}
		_Print(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_Print(root->_right);
	}
	bool Check(Node* root, int count, const int mode_count)
	{
		if (root == nullptr)
		{
			if (count != mode_count)
			{
				cout << "存在不同路径的黑色节点数量不同";
				return false;
			}
			return true;
		}

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

		if (root->_col == BLACK)
			count++;

		return Check(root->_left, count, mode_count) && Check(root->_right, count, mode_count);

	}
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值