【C++高阶】深入理解红黑树:数据结构与算法之美

📝个人主页🌹:Eternity._

⏩收录专栏⏪:C++ " 登神长阶 "

🤡往期回顾🤡:了解 AVL 树

🌹🌹期待您的关注 🌹🌹


❀红黑树

  • [📒1. 红黑树的概念](#📒1. 红黑树的概念)
  • [📙2. 红黑树结构](#📙2. 红黑树结构)
  • [📜3. 红黑树节点的定义](#📜3. 红黑树节点的定义)
  • [📚4. 红黑树的插入](#📚4. 红黑树的插入)
  • [📝5. 红黑树的验证](#📝5. 红黑树的验证)
  • [📘6. 红黑树与AVL树的比较](#📘6. 红黑树与AVL树的比较)
  • [📖7. 总结](#📖7. 总结)

前言: 在数据结构的浩瀚星空中,红黑树犹如一颗璀璨的明珠,以其独特的自平衡特性和高效的搜索能力,成为了计算机科学领域中不可或缺的一部分。红黑树,作为二叉搜索树的一种变体,通过引入节点颜色的概念和一系列复杂的旋转操作,巧妙地解决了传统二叉搜索树在极端情况下退化为链表的问题

红黑树的魅力并不仅仅在于其高效和实用。其背后所蕴含的算法思想和数据结构设计的智慧,更是值得我们深入学习和探索的宝贵财富。红黑树的实现过程充满了挑战与乐趣,它要求开发者不仅要掌握扎实的编程基础,还要具备敏锐的逻辑思维和严谨的数学分析能力

本篇文章,将带您一起揭开红黑树的神秘面纱。我们将从红黑树的基本概念出发,逐步深入到其内部结构、性质、操作原理以及实际应用等多个方面 。通过生动的图解、详细的步骤解析和丰富的实例代码,帮助您全面理解红黑树的精髓和魅力

让我们一起踏上学习 红黑树 的旅程,探索它带来的无尽可能!


📒1. 红黑树的概念

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

红黑树由Rudolf Bayer在1972年发明,最初被称为平衡二叉B树(Symmetric Binary B-trees),后来被Guibas和Robert Sedgewick修改为如今的"红黑树"。


红黑树的性质:

  • 每个结点不是红色就是黑色
  • 根节点是黑色的
  • 如果一个节点是红色的,则它的两个孩子结点是黑色的
  • 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
  • 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

📙2. 红黑树结构

为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点,如下:


📜3. 红黑树节点的定义

红黑树节点的定义通常包含以下几个关键部分:

基本元素:

  • _left:指向节点的左子节点的指针
  • _right:指向节点的右子节点的指针
  • _parent:指向节点的父节点的指针
  • _kv:一个结构体或配对(pair),包含节点的键值(key)和值(value)。这取决于红黑的具体用途,可能只包含键或包含键值对。

节点颜色(Color):

  • 在上面的定义中,_col 成员变量用于表示节点的颜色,通过 Color 枚举类型来定义,可以是 RED 或 BLACK。

构造函数:

  • 初始化一个新节点时,通常需要一个构造函数,它接受一个键值对(或仅键),并设置节点的左子节点、右子节点、父节点和颜色(初始化为红色)

节点定义示例(C++):

cpp 复制代码
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;  // pair
	
	Color _col; // 该节点的颜色

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

📚4. 红黑树的插入

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

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

在我们进行插入操作之前,我们先定义一个红黑树的类

红黑树定义示例(C++):

cpp 复制代码
template<class K, class V>
class RBTTree
{
	typedef BSTreeNode<K, V> Node;
public:
	// 其他未实现的成员函数
private:
	Node* _root = nullptr;	
};

红黑树的插入操作类似于我们之前AVL树的插入,只不过红黑树的插入操作涉及到旋转操作以及考虑其他节点的颜色,前面的操作还是一样的


🧩插入新节点

cpp 复制代码
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)
		{
			parent = cur;
			if (cur->_kv.first < kv.first)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				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;

		// 检测新节点插入后,红黑树的性质是否造到破坏

		return true;
	}

🌈检测红黑树是否造到破坏

(如果遭到破坏则对当前红黑树进行变色,旋转处理)
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

🌞情况一

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

解决方式:将parent,uncle改为黑,g改为红,然后把g当成cur,继续向上调整。


🌙情况二

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

解决方式: p为g的左孩子,cur为p的左孩子,则进行右单旋转p为g的右孩子,cur为p的右孩子,则进行左单旋转

parent、grandfather变色变色--> p变黑,g变红


⭐情况三

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


解决方式: p为g的左孩子,cur为p的右孩子,则针对p做左右双旋;p为g的右孩子,cur为p的左孩子,则针对p做右左旋转

cur、grandfather变色--> c变黑,p变红


检测红黑树是否造到破坏代码演示(C++):

cpp 复制代码
while (parent && parent->_col == RED)
{
	Node* grandfather = parent->_parent;
	if (parent == grandfather->_left)
	{
		//     g
		//   p   u
		// c
		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
				// 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
	{
		//     g
		//   u   p 
		//          c
		//
		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)
			{
				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;

红黑树的旋转和AVL树差不多,我们直接上代码回顾以下:
旋转代码示例(C++):

cpp 复制代码
void RotateL(Node* parent) // 左旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	subR->_left = parent;

	Node* Parentparent = parent->_parent;

	parent->_parent = subR;
	if (subRL)
	{
		subRL->_parent = parent;
	}
	// 判断parent是不是根节点
	if (_root == parent)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (parent == Parentparent->_left)
		{
			Parentparent->_left = subR;
		}
		else
		{
			Parentparent->_right = subR;
		}
		subR->_parent = Parentparent;
	}
}

void RotateR(Node* parent) // 右旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	parent->_left = subLR;
	if (subLR)
	{
		subLR->_parent = parent;
	}

	Node* Parentparent = parent->_parent;

	subL->_right = parent;
	parent->_parent = subL;

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

		subL->_parent = Parentparent;
	}
}

📝5. 红黑树的验证

红黑树的检测分为两步:

  • 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  • 检测其是否满足红黑树的性质

中序遍历代码演示(C++):

cpp 复制代码
void InOrder()
{
	_InOrder(_root);
	cout << endl;
}

void _InOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}
	_InOrder(root->_left);
	cout << root->_kv.first << " ";
	_InOrder(root->_right);
}

检测其是否满足红黑树的性质(C++):

cpp 复制代码
bool Check(Node* root, int blacknum, const int refVal)
{
	if (root == nullptr)
	{
		// 走到null之后,判断refVal和blacknum是否相等
		if (blacknum != refVal)
		{
			cout << "存在黑色节点数量不相等的路径" << endl;
			return false;
		}
		return true;
	}
	
	// 检测当前节点与其双亲是否都为红色
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << "有连续的红色节点" << endl;
		return false;
	}
	
	// 统计黑色节点的个数
	if (root->_col == BLACK)
	{
		++blacknum;
	}

	return Check(root->_left, blacknum, refVal)
		&& Check(root->_right, blacknum, refVal);
}

bool IsBalance()
{
	if (_root == nullptr)
	{
		// 空树也是红黑树
		return true;
	}

	if(_root->_col == RED)
	{
		return false;
	}

	// 参考值
	// 检测是否满足红黑树的性质,refVal用来记录路径中黑色节点的个数
	int refVal = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
		{
			++refVal;
		}
		cur = cur->_left;
	}
	// 获取任意一条路径中黑色节点的个数
	int blacknum = 0;
	
	return Check(_root, blacknum, refVal);
}

测试:

cpp 复制代码
int main()
{
	RBTree<int, int> t;
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for(auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.InOrder();
	cout << t.IsBalance() << endl;
	return 0;
}


没有出现问题,暂时认为此红黑树创建成功!


📘6. 红黑树与AVL树的比较

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

红黑树与AVL树在平衡策略、性能特性和实现复杂度等方面存在显著差异。在选择使用哪种数据结构时,需要根据具体的应用场景和需求进行权衡和选择。


📖7. 总结

关于红黑树的删除由于比较复杂,这里就不再讲解,有兴趣的同学可参考:《算法导论》或者《STL源码剖析》
红黑树的删除

随着我们对红黑树深入而细致的探讨,这段关于自平衡二叉搜索树的探索之旅也即将画上圆满的句号。红黑树,以其独特的魅力和卓越的性能,不仅在理论上为我们揭示了数据结构的精妙与复杂,更在实际应用中展现了其无可替代的价值与力量

我们从红黑树的基本概念出发,逐步深入了解了其性质、操作原理以及实际应用。我们见证了红黑树如何通过引入节点颜色和复杂的旋转操作,巧妙地维护了树的平衡,保证了在最坏情况下也能拥有高效的搜索、插入性能。深刻感受到了它在现代计算机科学中的重要性

红黑树的故事并未结束。随着技术的不断发展和应用场景的不断拓展,红黑树也将继续发挥其独特的作用,为我们解决更多复杂的问题和挑战。同时,红黑树所蕴含的算法思想和数据结构设计的智慧也将激励着我们不断学习和探索,追求更加高效、优雅和简洁的编程之道

让我们充分理解红黑树,继续在数据结构和算法的海洋中遨游,不断挖掘计算机科学的奥秘,为未来的技术创新和进步贡献自己的力量。愿每一位学习者都能在求知的道路上不断前行,收获满满的智慧与快乐


希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

相关推荐
Komorebi.py29 分钟前
【Linux】-学习笔记05
linux·笔记·学习
Ajiang28247353042 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
盼海2 小时前
排序算法(五)--归并排序
数据结构·算法·排序算法
幽兰的天空2 小时前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
Theodore_10225 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
工业甲酰苯胺6 小时前
Redis性能优化的18招
数据库·redis·性能优化
‘’林花谢了春红‘’6 小时前
C++ list (链表)容器
c++·链表·list
----云烟----7 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024067 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic7 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端