详解红黑树【C++实现】

前言

之前介绍的AVL树由于严格要求左右子树高度差不能>1,因此这势必导致需要经过许多次的旋转,导致效率降低。
而红黑树通过巧妙的设计,能有效避免多次旋转也能达到平衡。
维基百科的介绍

概念

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

也就是最长路径不超过最短路径2倍。

在这里插入图片描述

性质

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

满足上述性质,就能保证红黑树的最长路径不超过最短路径2倍
最短:全黑。
最长:一黑一红间隔。
由于要保证每条路径的黑色节点数量相等,又最长的是一黑一红间隔,因此最长节点数量最多就是最短的2倍。

红黑树节点

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)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}
};

在节点的定义中,为什么要将节点的默认颜色给成红色的?
这跟新增节点新增红色的一样。👇

插入

新增节点,新增黑色还是红色?
1、新增红色,可能破坏规则3
2、新增黑色,一定破坏规则4,并且规则4很难维护。

二叉搜索树

红黑树是在二叉搜索树的基础上加上其平衡限制条件而产生的。
因此,第一步是按照二叉搜索树的规则插入新节点。

template<class K, class V>
struct 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; // 插入黑色一定会破坏规则4,而且规则4很难维护
		if (parent->_kv.first < kv.first)
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;
		// 红黑树处理 ...
		
		_root->_col = BLACK; // 最后要确保根节点为黑色
		return true;
	}

private:
	Node* _root = nullptr;
};

检测是否破坏红黑树

第二步,检测新插入节点是否破坏红黑树性质。

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论。
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

情况一

cur为红,p为红,g为黑,u存在且为红。
解决方式:将p、u改为黑,g改为红,然后把g当成cur,继续向上调整。
在这里插入图片描述

情况二(单旋)

cur为红,p为红,g为黑,u不存在或者u存在且为黑。
解决方式:单旋加变色

在这里插入图片描述

情况三(双旋)

cur为红,p为红,g为黑,u不存在/u存在且为黑。
其实就是形成了g p cur 的折线,针对折线,我们在AVL树中也处理过了,其实就是2次单旋。
需要注意的是:
情况二和情况三旋转+变色之后,这颗子树已经不违反红黑树规则了,并且相比新增节点前,黑色节点数量不变,也就是不影响上层。
在这里插入图片描述

上述3种情况都是考虑 parent 是在 grandfather 左边的情况,右边的情况也是类似的。

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; // 插入黑色一定会破坏规则4,而且规则4很难维护
	if (parent->_kv.first < kv.first)
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;
	// 红黑树处理 ...
	while (parent != nullptr && parent->_col == RED) // 存在连续红色节点
	{
		Node* grandFather = parent->_parent;
		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 // uncle 不存在或者 存在且为黑  旋转处理
			{
				if (cur == parent->_left) // 单旋
				{
					//     g
					//   p
					// c
					RotateR(grandFather);
					parent->_col = BLACK;
					grandFather->_col = RED;
				}
				else // 双旋
				{
					//     g
					//   p
					//     c
					RotateL(parent);
					RotateR(grandFather);
					cur->_col = BLACK;
					grandFather->_col = RED;
				}
				break; // 旋转完成不需要再处理了
			}
		}
		else // parent == grandFather->_right
		{
			Node* uncle = grandFather->_left;

			if (uncle && uncle->_col == RED) // 情况一 存在且为红
			{
				parent->_col = uncle->_col = BLACK; // 变色
				grandFather->_col = RED;
				cur = grandFather; // 继续向上处理
				parent = cur->_parent;
			}
			else // uncle 不存在或者 存在且为黑  旋转处理
			{
				if (cur == parent->_right) // 单旋
				{
					// g
					//   p
					//     c
					RotateL(grandFather);
					parent->_col = BLACK;
					grandFather->_col = RED;
				}
				else // 双旋
				{
					// g
					//   p
					// c
					RotateR(parent);
					RotateL(grandFather);
					cur->_col = BLACK;
					grandFather->_col = RED;
				}
				break; // 旋转完成不需要再处理了
			}
		}
	}
	_root->_col = BLACK; // 最后要确保根节点为黑色
	return true;
}

判断是否为红黑树

bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
{
	// 走到null之后,判断k和black是否相等
	if (nullptr == pRoot)
	{
		if (k != blackCount)
		{
			cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
			return false;
		}
		return true;
	}

	// 统计黑色节点的个数
	if (BLACK == pRoot->_col)
		++k;

	// 检测当前节点与其双亲是否都为红色
	if (RED == pRoot->_col && pRoot->_parent && pRoot->_parent->_col == RED)
	{
		cout << "违反性质三:存在连在一起的红色节点" << endl;
		return false;
	}

	return _IsValidRBTree(pRoot->_left, k, blackCount) &&
		_IsValidRBTree(pRoot->_right, k, blackCount);
}
bool IsValidRBTree() // 检查当前红黑树是否满足红黑树的几条规则
{
	// 空树也是红黑树
	if (nullptr == _root)
		return true;

	// 检测根节点是否满足情况
	if (BLACK != _root->_col)
	{
		cout << "违反红黑树性质二:根节点必须为黑色" << endl;
		return false;
	}

	// 获取任意一条路径中黑色节点的个数 -- 作为比较的基准值
	size_t blackCount = 0;
	Node* pCur = _root;
	while (pCur)
	{
		if (BLACK == pCur->_col)
			blackCount++;

		pCur = pCur->_left; // 取最左路径黑色节点个数作为基准值
	}

	// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
	size_t k = 0;
	return _IsValidRBTree(_root, k, blackCount);
}

求高度

void Height()
{
	cout << "最长路径:" << _maxHeight(_root) << endl;
	cout << "最短路径:" << _minHeight(_root) << endl;
}
int _maxHeight(Node* root)
{
	if (root == nullptr)
		return 0;
	int lh = _maxHeight(root->_left);
	int rh = _maxHeight(root->_right);
	return lh > rh ? lh + 1 : rh + 1;
}

int _minHeight(Node* root)
{
	if (root == nullptr)
		return 0;
	int lh = _minHeight(root->_left);
	int rh = _minHeight(root->_right);
	return lh < rh ? lh + 1 : rh + 1;
}

测试

void TestRBTree1()
{
	//int a[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
	int a[] = { 30, 29, 28, 27, 26, 25, 24, 11, 8, 7, 6, 5, 4, 3, 2, 1 };
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.levelOrder();
	t.InOrder();
	t.Height();
}

在这里插入图片描述

void TestRBTree2()
{
	const size_t N = 1024 * 1024;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; ++i)
	{
		//v.push_back(rand());
		v.push_back(i);
	}

	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}

	//t.levelOrder();
	//cout << endl;
	cout << "是否平衡?" << t.IsValidRBTree() << endl;
	t.Height();

	//t.InOrder();
}

在这里插入图片描述

AVL&红黑树比较

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

尾声

🌹🌹🌹

写文不易,如果有帮助烦请点个赞~ 👍👍👍

Thanks♪(・ω・)ノ🌹🌹🌹

😘😘😘

👀👀由于笔者水平有限,在今后的博文中难免会出现错误之处,本人非常希望您如果发现错误,恳请留言批评斧正,希望和大家一起学习,一起进步ヽ( ̄ω ̄( ̄ω ̄〃)ゝ,期待您的留言评论。
附GitHub仓库链接

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值