红黑树插入与调整

红黑树简介

红黑树(Red-Black Tree)是一类自平衡二叉搜索树 ,由鲁道夫・拜尔于 1972 年提出,后经利奥尼达斯・吉巴斯与罗伯特・塞奇威克完善命名与理论体系。作为计算机科学中经典的动态有序数据结构,它在二叉搜索树(BST)的基础上引入节点颜色标记与严格约束规则,通过颜色修正、左旋、右旋三类基础操作实现动态平衡,有效规避了普通二叉搜索树在极端插入、删除场景下退化为线性链表的缺陷。

红黑树对树中节点定义红、黑两种颜色,并强制满足五条核心性质,以此保证从根节点到任意叶节点的所有路径,最长路径长度不超过最短路径长度的 2 倍:

  1. 每个节点仅为红色或黑色;
  2. 根节点必为黑色;
  3. 所有虚拟空叶节点(NIL 节点)均为黑色;
  4. 若某节点为红色,则其两个子节点必须均为黑色,即树中不存在连续的红色节点;
  5. 从任意节点出发,抵达其下属所有 NIL 叶节点的每条路径,所包含的黑色节点数量完全相等。

基于上述约束,含n个有效节点的红黑树,其树高上界为 2log2​(n+1)。这一有界树高特性决定了其查找、插入、删除三大基础操作的时间复杂度均稳定为 O(logn),具备优异的最坏时间复杂度表现。

当执行节点插入与删除操作时,原有红黑性质大概率被破坏,算法会通过旋转 调整树的拓扑结构,配合颜色重绘修复约束条件。根据违规场景的差异,插入操作主要分为父节点红、叔父节点红 / 黑等分支处理,删除操作因涉及黑色节点移除引发的 "黑高失衡",修复逻辑更为复杂,共衍生多种局部调整范式。

在工程应用领域,红黑树凭借均衡的性能与较低的平衡维护开销,被广泛集成于主流系统与标准库:C++ STL 中的std::mapstd::set,Java 集合框架中的TreeMapTreeSet,以及 Linux 内核进程调度、内存管理、文件系统索引等核心模块,均以红黑树作为底层数据结构,承担有序元素的动态维护与高效检索任务。相较于 AVL 树,红黑树平衡约束更宽松,旋转操作频次更少,在频繁增删的业务场景中综合性能更具优势,是目前工业界应用最广泛的自平衡二叉搜索树之一。

秩序之翼------红黑树的规定(下面提及的规则以这个为准)

红黑树的规定如下:

1.每个节点都有颜色,非黑即红

2.根节点和叶子节点的孩子节点(即叶子节点孩子指针指向的空节点)为黑色

3.若某节点为红色,它的孩子节点不可以为红色。

4.引入定义,从任意一个固定节点出发一条到空节点的通路中黑色节点的个数为hb(height of black nodes),当然叶子节点链接的空节点也要算,从任意一个固定节点出发每一条到空节点的通路中,hb相同。

红黑树中,从根节点出发到空节点的每一通路中,节点个数最多的通路节点个数不超过节点个数最少的通路的节点个数的两倍。

证明过程:

由规则4,两条通路黑色节点个数相同,最短路径就全是黑色节点,最长路径是在最短路径的基础之上尽可能多的加入红色节点,由规则3,最长路径就是黑红节点交替的状态,由于规则2,一头一尾必须是黑色节点,新插入红色节点的个数必然不超过已有的黑色节点的个数,结论得证。

红黑树的节点封装

cpp 复制代码
enum color{
	black,
	red
};
template<class K, class V>
struct node
{
	node* _left;
	node* _right;
	node* _parent;
	pair<K, V> _kv;
	color _col;
	node(K key, V val) :_left(nullptr), _right(nullptr), _parent(nullptr), _kv(key, val), _col(red)
	{

	}
	node() = default;
};

每个节点给到了六个成员变量:

1.左、右孩子指针:用于树状结构的遍历

2.双亲结点指针:用于回溯(插入,和调整颜色过程要使用)

3.节点用于存储数据信息的键值对

4.自生颜色:标记颜色

5.构造函数,用于new节点时调用

有了一个节点的结构,我们通过类来封装红黑树的方法:

cpp 复制代码
template<class K,class V>
class RBtree
{
	typedef node<K, V> node;
public:
	//相关方法
private:
	
	node* _root;//记录根节点信息方便从根访问整棵树
	
};

红黑树的插入操作

由于插入的部分逻辑和查找相同,我们首先实现按值查找:

定义一个用于遍历的指针,从树的根节点开始沿从根节点到空节点的某条通路遍历,找到该节点。这条通路如何确定?按照二叉搜索树的规则:当给定的key值比存放在当前节点的key值大,接下来遍历该节点右孩子,当给定的key值比存放在当前节点的key值小,接下来遍历该节点左孩子,如果相同,停止遍历(这里实现的不允许插入相同key值的红黑树)

cpp 复制代码
node* find(K key)
{
	if (_root == nullptr)return nullptr;
	else
	{
		node* cur = _root;
		while(cur)
		{
			if (cur->_kv.first < key)cur = cur->_right;
			else if (cur->_kv.first > key)cur = cur->_left;
			else return cur;
		}
		return nullptr;
	}
}

我们实现的是按值插入,所以在插入之前我们要找到新插入数据节点应该存放的位置,那么如何找到这个位置?

如果树是空树,不用查找,直接构造一个黑色节点,将该节点指针给到_root.其余一般情况,我们按照和查找中的逻辑(这个key值不在已有的树中,自然会遍历到空节点),我们在来一个指针记录遍历节点指针的双亲节点位置,构造新节点,通过两个指针链接即可。

新插入节点的颜色?

一定是红色,如果插入节点的颜色是黑色,那么处该路径之外,所有从根节点到空节点的路径的hb都相同,要调整的幅度较大,如果是红色,只需要沿着从根节点到该节点的路径反向调整即可。

cpp 复制代码
bool insert(K key, V val)
{

	if (_root == nullptr)
	{
		_root = new node(key, val);
		_root->_col = black;
	}
	node* cur=_root;
	node* pcur = nullptr;
	//找到合适的插入节点位置
	while (cur)
	{
		if (cur->_kv.first < key)pcur = cur, cur = cur->_right;
		else if (cur->_kv.first > key)pcur = cur, cur = cur->_left;
		else return false;
	}
    //链接节点
	cur = new node(key, val);
	cur->_parent = pcur;
	if (pcur->_kv.first < key)pcur->_right = cur;
	else pcur->_left = cur;

	//调整过程
}

这里有一个小细节:创造新节点之后不仅要将原来的叶子节点和新插入节点链接,还要将新插入节点的双亲节点通过新插入节点的_parent指针链接。

接下来是红黑树的精华:调整环节,也是红黑树和普通二叉搜索树的区别所在,红黑树就是在插入和删除环节加入调整的算法来确保整棵树所有节点还满足树的上述4条规则。

调整部分:

如果新插入节点的双亲结点是黑色,没有违反上述任何规则,不用调整,结束插入操作

反之,调整算法正式登场:

由于对称性我们只需要讨论插入时双亲节点是它的双亲节点(之后叫这个节点祖父节点)的左孩子,(祖父节点的右孩子称为叔叔节点),并且由于是双亲节点是红色才会违反规则进入到调整算法的环节,那么下面基于上述条件的插入情况有如下三种:

记祖父节点为G(grandfather),双亲结点为P(parent),叔叔节点为U(uncle),当前节点为C(child)

那么插入的情况就可以分为这八种:

1.叔叔存在且为红,C是P的外侧孩子

2.叔叔存在且为红,C是P的内侧孩子

3.叔叔存在且为黑,C是P的外侧孩子

4.叔叔存在且为黑,C是P的内侧孩子

5.叔叔不存在,C是P的外侧孩子

6.叔叔不存在,C是P的内侧孩子

根据探讨调整算法,我们发现1,2;3,5;4,6;的调整算法相同,因此分为三种情况讨论:1.叔叔存在且为红 2.叔叔不存在c在外侧或叔叔存在且为黑 c在外侧3.叔叔不存在c在内侧或叔叔存在且为黑 c在内侧

情况1.叔叔存在且为红------变色+向上调整

(只是示意图:P U 可同时添加一个hb小于1的子树,读者可自行画图"眼见为实")

只需要将p U 变为黑色, c变为红色,但是千万要小心------由规则3:不可以出现两个连续的红色节点,所以下面的调整中当一个黑色节点变红时都要格外小心,必然伴随着向上调整,检查G的双亲节点是否是红色节点,如果是,将G变成C再走一次这个算法。

不太理解向上继续处理的读者可以看一下下面这个示例:

2.叔叔不存在c在外侧或叔叔存在且为黑 c在外侧------单旋加变色

首先要引入单旋的概念------上一期已经讲过->AVL树与旋转

红黑树的旋转代码和AVL树别无二异,只是不需要更新平衡因罢了。

以上这种情况只需要将G右单旋,变为红色之后再将P变色,由于现在整颗子树的根节点为黑色,不需要继续向上处理。

情况3.叔叔不存在c在内侧或叔叔存在且为黑 c在内侧

只需要对G做一个左右双旋,G变为红色,C变为黑色,也不需要继续向上调整。

有了上述关于P是G左孩子的相关处理,P是G的右孩子的处理方式只需要把判断逻辑和旋转方向调整一下便可迎刃而解。

为了防止向上调整过程中将根节点颜色变为红,整个调整部分代码结束之后将根节点颜色赋值为黑色即可。

插入环节调整部分代码:

cpp 复制代码
if (cur->_parent->_col == black)return true;//双亲节点为黑,插入结束

else //回溯,向上调整
{
	node* child = cur;
	node* parent = cur->_parent;
	
	while (parent&&parent->_col==red)//grandfather 一定存在,不然parent一定为黑
	{
		node* grandfather = parent->_parent;
		if (parent ==grandfather->_left)
		{
			node* uncle = grandfather->_right;
			if (uncle->_col == red)//情况1:叔叔存在且为红,父亲为红,祖父为黑
			{//变色
				grandfather->_col = red;
				uncle->_col = black;
				parent->_col = black;
				if (grandfather->_parent&& grandfather->_parent->_col == black)break;
				else//继续向上处理
				{
					child = grandfather;
					parent = child->_parent;
				}
			}
			else if ((uncle != nullptr && uncle->_col == black) || uncle == nullptr)
				//情况2:叔叔存在且为黑,叔叔不存在
				//       g
				//   p       u
				//c
			{
				if (child == parent->_left)//单旋
				{
					rotateR(grandfather);
					parent->_col = black;
					grandfather->_col = red;
					break;
				}
				else//双旋
				{
					rotateL(parent);
					rotateR(grandfather);
					child->_col = black;
					grandfather->_col = red;
				}
			}
		}
		else
		{
			node* uncle = grandfather->_left;
			if (uncle && uncle->_col == red)//叔叔存在且为红
			{
				grandfather->_col = red;
				parent->_col = uncle->_col = black;
				if (grandfather->_parent&& grandfather->_parent->_col == red)
				{
					child = grandfather;
					parent = grandfather->_parent;
				}
				else
				{
					break;
				}
			}
			else if ((uncle && uncle->_col == black) || uncle == nullptr)//叔叔存在且为黑或叔叔不存在
			{
				//单旋
				if(parent->_right==child)
				{
					rotateL(grandfather);
					grandfather->_col = red;
					parent->_col = black;
					break;
				}
				//双旋
				if (parent->_left == child)
				{
					rotateR(parent);
					rotateL(grandfather);
					grandfather->_col = red;
					child->_col = black;
					break;
				}
			}
		}
	}
}

_root->_col = black;
return true;

红黑树的验证

检验逻辑:

cpp 复制代码
bool check(node* root,int bh,int Bnum)
{
	if (root == nullptr)//根节点,判断hb是否和基准值相等
	{
		if (bh == Bnum)return true;
		else
		{
			cout << "存在bh不相同的两条路径\n" << endl;
			return false;
		}
	}
	if (root->_col == black)bh++;
	if (root->_col == red && root->_parent->_col ==  red) return false;
	return check(root->_left,bh,Bnum)&&check(root->_right, bh, Bnum);

}bool isRBtree()
{
	if (_root == nullptr)return true;
	if (_root->_col == red) return false;
	int std = 0;
	node* cur=_root;
	while (cur!=nullptr)
	{
		if (cur->_col == black)std++;
		cur = cur->_left;
	}
	int bh = 0;
	return check(_root, bh, std);
}

本套代码用于校验一棵二叉树是否满足红黑树的全部约束规则,整体采用递归遍历思路,结合前置校验与递归检查两大模块实现,具体逻辑如下:

  1. 空树判定若整棵树为空树,直接判定为合法红黑树。

  2. 根节点专项校验若树非空,首先检查根节点颜色:红黑树规定根节点必须为黑色,若根节点为红色,直接判定不合法。

  3. 获取基准黑高 从根节点出发,沿最左侧路径 遍历至空节点,统计该路径上黑色节点的总数,将其作为基准黑高,用于后续统一校验所有路径的黑高一致性。

  4. 递归校验函数 check 核心逻辑 该函数递归遍历整棵树,同时完成黑高hb一致性校验连续红节点校验两大核心规则:

    • 递归终止条件:遍历到空节点时,对比当前路径累计黑高与基准黑高。二者相等则当前路径合法;不相等则输出提示并返回不合法。
    • 黑色节点处理:若当前节点为黑色,将当前路径的黑高计数加一。
    • 连续红节点校验:若当前节点为红色,向上检查其父节点颜色,若父节点同样为红色,违反规则,直接返回不合法。
    • 递归递推:依次递归校验当前节点的左子树与右子树,只有左右子树均合法,当前分支才算合法。
  5. 整体判定规则整棵树需同时满足:根节点为黑色、所有路径黑高统一、树中无连续红色节点,三者全部成立,才是合法红黑树。

红黑树整体代码:

cpp 复制代码
//RBtree.h
#pragma once
#include<iostream>
using namespace std;

enum color{
	black,
	red
};
template<class K, class V>
struct node
{
	node* _left;
	node* _right;
	node* _parent;
	pair<K, V> _kv;
	color _col;
	node(K key, V val) :_left(nullptr), _right(nullptr), _parent(nullptr), _kv(key, val), _col(red)
	{

	}
	node() = default;
};
template<class K,class V>
class RBtree
{
	typedef node<K, V> node;
public:
	bool isRBtree()
	{
		if (_root == nullptr)return true;
		if (_root->_col == red) return false;
		int std = 0;
		node* cur=_root;
		while (cur!=nullptr)
		{
			if (cur->_col == black)std++;
			cur = cur->_left;
		}
		int bh = 0;
		return check(_root, bh, std);
	}
	RBtree():_root(nullptr)
	{

	}
	~RBtree()
	{
		destruct(_root);
		_root = nullptr;
	}
	void inOrder()
	{
		_inOrder(_root);
	}
	bool insert(K key, V val)
	{

		if (_root == nullptr)
		{
			_root = new node(key, val);
			_root->_col = black;
		}
		node* cur=_root;
		node* pcur = nullptr;
		//找到合适的插入节点位置
		while (cur)
		{
			if (cur->_kv.first < key)pcur = cur, cur = cur->_right;
			else if (cur->_kv.first > key)pcur = cur, cur = cur->_left;
			else return false;
		}
	    //链接节点
		cur = new node(key, val);
		cur->_parent = pcur;
		if (pcur->_kv.first < key)pcur->_right = cur;
		else pcur->_left = cur;
		
		if (cur->_parent->_col == black)return true;//双亲节点为黑,插入结束

		else //回溯,向上调整
		{
			node* child = cur;
			node* parent = cur->_parent;
			
			while (parent&&parent->_col==red)//grandfather 一定存在,不然parent一定为黑
			{
				node* grandfather = parent->_parent;
				if (parent ==grandfather->_left)
				{
					node* uncle = grandfather->_right;
					if (uncle->_col == red)//情况1:叔叔存在且为红,父亲为红,祖父为黑
					{//变色
						grandfather->_col = red;
						uncle->_col = black;
						parent->_col = black;
						if (grandfather->_parent&& grandfather->_parent->_col == black)break;
						else//继续向上处理
						{
							child = grandfather;
							parent = child->_parent;
						}
					}
					else if ((uncle != nullptr && uncle->_col == black) || uncle == nullptr)
						//情况2:叔叔存在且为黑,叔叔不存在
						//       g
						//   p       u
						//c
					{
						if (child == parent->_left)//单旋
						{
							rotateR(grandfather);
							parent->_col = black;
							grandfather->_col = red;
							break;
						}
						else//双旋
						{
							rotateL(parent);
							rotateR(grandfather);
							child->_col = black;
							grandfather->_col = red;
						}
					}
				}
				else
				{
					node* uncle = grandfather->_left;
					if (uncle && uncle->_col == red)//叔叔存在且为红
					{
						grandfather->_col = red;
						parent->_col = uncle->_col = black;
						if (grandfather->_parent&& grandfather->_parent->_col == red)
						{
							child = grandfather;
							parent = grandfather->_parent;
						}
						else
						{
							break;
						}
					}
					else if ((uncle && uncle->_col == black) || uncle == nullptr)//叔叔存在且为黑或叔叔不存在
					{
						//单旋
						if(parent->_right==child)
						{
							rotateL(grandfather);
							grandfather->_col = red;
							parent->_col = black;
							break;
						}
						//双旋
						if (parent->_left == child)
						{
							rotateR(parent);
							rotateL(grandfather);
							grandfather->_col = red;
							child->_col = black;
							break;
						}
					}
				}
			}
		}
		
		_root->_col = black;
		return true;
	}
node* find(K key)
{
	if (_root == nullptr)return nullptr;
	else
	{
		node* cur = _root;
		while(cur)
		{
			if (cur->_kv.first < key)cur = cur->_right;
			else if (cur->_kv.first > key)cur = cur->_left;
			else return cur;
		}
		return nullptr;
	}
}

private:
	bool check(node* root,int bh,int Bnum)
	{
		if (root == nullptr)//根节点,判断hb是否和基准值相等
		{
			if (bh == Bnum)return true;
			else
			{
				cout << "存在bh不相同的两条路径\n" << endl;
				return false;
			}
		}
		if (root->_col == black)bh++;
		if (root->_col == red && root->_parent->_col ==  red) return false;
		return check(root->_left,bh,Bnum)&&check(root->_right, bh, Bnum);

	}
	node* _root;
	//右单旋
	void rotateR(node* root)
	{
		node* proot = root->_parent;
		node* rootL = root->_left;
		node* rootLR = root->_left->_right;
		//重新选定根节点
		if (proot == nullptr)
		{
			_root = rootL;
			rootL->_parent = nullptr;
		}
		else if (proot->_left == root)
		{
			proot->_left = rootL;
			rootL->_parent = proot;
		}
		else
		{
			proot->_right = rootL;
			rootL->_parent = proot;
		}
		//链接root和rootLR
		root->_left = rootLR;
		if (rootLR)rootLR->_parent = root;
		//链接rootL和root
		rootL->_right = root;
		root->_parent = rootL;
		
	}
	//左单旋
	void rotateL(node* root)
	{
		node* proot = root->_parent;
		node* rootR = root->_right;
		node* rootRL = rootR->_left;
		//链接proot和rootR
		if (proot == nullptr)
		{
			_root = rootR;
			rootR->_parent = nullptr;
		}
		else if (proot->_left == root)
		{
			proot->_left = rootR;
			rootR->_parent = proot;
		}
		else if (proot->_right == root)
		{
			proot->_right = rootR;
			rootR->_parent = proot;
		}
		//链接root和rootRL
		root->_right = rootRL;
		if (rootRL)rootRL->_parent = root;
		//链接root和rootR
		rootR->_left = root;
		root->_parent = rootR;
		
	}
	//中序遍历
	void _inOrder(node* root)
	{
		if (root == nullptr)return;
		_inOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_inOrder(root->_right);
	}
	//后序删除
	void destruct(node* root)
	{
		if (root == nullptr)return;
		destruct(root->_left);
		destruct(root->_right);
		delete root;
	}
};
//test.cpp
#include"RBtree.h"
#include <iostream>
using namespace std;

// 测试函数声明
void testInsertAndInOrder();
void testFind();
int main()
{
	cout << "========== 红黑树功能测试 ==========" << endl;
	cout << "==========插入和中序遍历============" << endl;
	testInsertAndInOrder();   // 测试插入+中序遍历
	system("pause");
	cout << "==============查找测试==============" << endl;
	testFind();
    return 0;
}

// 测试1:插入节点 + 中序遍历(验证二叉搜索树有序性)
void testInsertAndInOrder()
{
	cout << endl << "【测试1:插入与中序遍历】" << endl;
	RBtree<int, string> rb;

	// 插入测试数据
	rb.insert(10, "ten");
	rb.insert(20, "twenty");
	rb.insert(5, "five");
	rb.insert(15, "fifteen");
	rb.insert(30, "thirty");
	rb.insert(3, "three");
	rb.insert(7, "seven");

	cout << "中序遍历结果(有序即正确):" << endl;
	rb.inOrder();
	if (rb.isRBtree())
		cout << "这是一棵红黑树" << endl;
}

// 测试2:查找节点(存在/不存在两种情况)
void testFind()
{
	cout << endl << "【测试2:查找功能】" << endl;
	RBtree<int, string> rb;
	rb.insert(10, "ten");
	rb.insert(20, "twenty");
	rb.insert(5, "five");
	node<int, string>* p;
	p = rb.find(10);
	if (p)
		cout << p->_kv.first << ":" << p->_kv.second << endl;
	else
		cout << "no find" << endl;
	p = rb.find(20);
	if (p)
		cout << p->_kv.first << ":" << p->_kv.second << endl;
	else
		cout << "no find" << endl;
	p = rb.find(27);
	if (p)
		cout << p->_kv.first << ":" << p->_kv.second << endl;
	else
		cout << "no find" << endl;
	
}