红黑树是一种自平衡二叉查找树,通过为节点赋予红/黑颜色属性,并遵循五条核心规则(如根节点为黑、红色节点子节点必为黑、任意路径黑色节点数相同等),确保树的高度差不超过两倍,从而在插入、删除、查找操作中维持O(log n)的时间复杂度。其平衡性通过旋转(左旋/右旋)和颜色调整实现,而非严格保持子树高度差,因此平衡维护成本低于AVL树。
声明:上述树为标准的红黑树,可能有人会思考:叶子结点的孩子为空节点,它不是黑色的呀,这不是违反规则了吗?《算法导论》补充了每个叶子结点(NIL)都是黑色的规则。NIL也会被称为外部节点。
思考一下:红黑树如何保证最长路径的长度不超过最短路径的2倍???
如图:
假设N是红黑树中节点数量,h最短路径的长度,
-1 <= N <
-1,由此推出 h = logN,最长路径是最短路径的两倍,即意味着红黑树增删查改最坏情况下,遍历最深的深度,即2 * logN,时间复杂度为O(logN)。
//节点颜色
enum Colour
{
RED,
BLACK
};
// 这⾥我们默认按key/value结构实现,三叉链
// 红黑树节点结构
template<class K, class V>
struct RBTreeNode
{
// 这⾥更新控制平衡也要加⼊parent指针
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
{}
};
//红黑树结构
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
private:
Node* _root = nullptr;
};
红黑树用三叉链的数据结构实现,红黑树节点中的数据使用pair类型的数据存放,使用pair类型的数据申请红黑树节点。
通过上述分析,得出的伪代码如下:
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;//插入相同的key值,直接返回false。
}
}
//将新插入的节点与父节点相连
//插入黑色节点一定不满足,很难维护,所以咱们插入红色节点
cur = new Node(kv);
cur->_col = RED;
if (parent->_kv.first < kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;//将当前新插入的节点与parent节点进行相连。
//处理不满足红黑树规则的情况,下面继续补充。
下面都是parent都是grandfather的左孩子。
另外两种不再描述,读者自己想想,锻炼思考能力。 下面以hb==1,表示存在1个黑色节点的红黑树。
d/e/f为x/y/z/m中任意一种,情况处理一致,将a/b变成黑,c变成红,然后继续向上处理。
伪代码如下:
Node* grandfather = parent->_parent;
if (grandfather->_left == parent)
{
// g
// p u
//c
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)
{
//变色
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
//继续向上处理
cur = grandfather;
parent = cur->_parent;
}
当uncle存在且为红时,只需变色即可。
上述情况单纯变色无法解决问题,以g为旋转点进行右旋,然后将c/g变为红色,p变为黑色。
场景1:以g为旋转点进行右单旋,再把g变为红色,p变为黑色。
现以parent为旋转点进行左单旋,在意grandfather为旋转点进行右单旋。旋转完成之后跳出循环即可。
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
//uncle不存在,则c一定是新增节点,假设c不是新增节点,则c一定是黑色,违反规则3
// g
// p u
// c
RotateL(parent);
RotateR(grandfather);
cur->_col = RED;
grandfather->_col = RED;
}
break;
parent是grandfather右孩子的处理情况一致。本人就不再重复写了,直接上代码(如下):
else//grandfather右孩子为parent
{
// g
// u p
//c
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
grandfather->_col = RED;
uncle ->_col= parent->_col = BLACK;
cur = grandfather;
parent = cur->_parent;
}
else//uncle不存在,或者uncle存在且为黑
{
//uncle不存在,则c一定是新增节点,假设c不是新增节点,则c一定是黑色,违反规则3
// g黑 g p
// u黑 p红 u p g c
// c红 c 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;
}
整合代码如下:
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (grandfather->_left == parent)
{
// g
// p u
//c
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)
{
//变色
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
//继续向上处理
cur = grandfather;
parent = cur->_parent;
}
else//uncle不存在,或者uncle存在且为黑
{
//uncle不存在,则c一定是新增节点,假设c不是新增节点,则c一定是黑色,违反规则3
// g
// p
// c
//uncle不存在,或者uncle存在且为黑下面都可以解决上述两个场景
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
//uncle不存在,则c一定是新增节点,假设c不是新增节点,则c一定是黑色,违反规则3
// g
// p u
// c
RotateL(parent);
RotateR(grandfather);
cur->_col = RED;
grandfather->_col = RED;
}
break;
}
}
else//grandfather右孩子为parent
{
// g
// u p
//c
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
grandfather->_col = RED;
uncle ->_col= parent->_col = BLACK;
cur = grandfather;
parent = cur->_parent;
}
else//uncle不存在,或者uncle存在且为黑
{
//uncle不存在,则c一定是新增节点,假设c不是新增节点,则c一定是黑色,违反规则3
// g黑 g p
// u黑 p红 u p g c
// c红 c 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;
}
}
}
_root->_col = BLACK;
return true;
}
按照二叉搜索树的规则,插入,过程如下:
Node* Find(const K& key)
{
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;
}
思路:以最左路径计算出此路径的黑色节点个数记为h1为参考值,依次递归求其它路径的黑色节点个数,比较各个路径黑色节点个数与h1的比值,发现任意一条路径不同直接返回false即可,所有路径遍历完都相等且任意一条路径的不存在连续的红色节点返回true即可。
bool Check(Node* root, int blackNum, int refNum)
{
if (root == nullptr)
{
if (blackNum != refNum)
{
cout << "存在黑色结点数量不相等的路径" << endl;
return false;
}
return true;
}
//任意一条路径连续相同的红色节点他都是不是红黑树,也就不是平衡树
if (root->_col == RED && root->_parent->_col == RED)
{
cout << root->_kv.first << "存在连续的红色结点" << endl;
return false;
}
if (root->_col == BLACK)
{
blackNum++;
}
return Check(root->_left, blackNum, refNum)
&& Check(root->_right, blackNum, refNum);
}
bool IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col == RED)
return false;
// 参考值
int refNum = 0;//选择一个最左边的树这条路径有多少个黑色节点,其它路径一律以它为基准进行比较
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
return Check(_root, 0, refNum);
}
测试代码:
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
#include"RBTree.h"
#include"AVLTree.h"
// 测试代码
void TestRBTree1()
{
RBTree<int, int> t;
// 常规的测试用例
int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
// 特殊的带有双旋场景的测试用例
//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : a)
{
/*if (e == 14)
{
int x = 0;
}*/
t.Insert({ e, e });
//cout << "Insert:" << e << "->";
//cout << t.IsBalanceTree() << endl;
}
t.InOrder();
cout << t.IsBalance() << endl;
}
// 插入一堆随机值,测试平衡,顺便测试一下高度和性能等
void TestRBTree2()
{
const int N = 10000000;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; i++)
{
v.push_back(rand() + i);
}
size_t begin2 = clock();
RBTree<int, int> t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
}
size_t end2 = clock();
cout << t.IsBalance() << endl;
cout << "RBTree Insert:" << end2 - begin2 << endl;
cout << "RBTree Height:" << t.Height() << endl;
cout << "RBTree Size:" << t.Size() << endl;
size_t begin1 = clock();
// 确定在的值
/*for (auto e : v)
{
t.Find(e);
}*/
// 随机值
for (size_t i = 0; i < N; i++)
{
t.Find((rand() + i));
}
size_t end1 = clock();
cout << "RBTree Find:" << end1 - begin1 << endl << endl;
}
void TestAVLTree2()
{
const int N = 10000000;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; i++)
{
v.push_back(rand() + i);
}
size_t begin2 = clock();
AVLTree<int, int> t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
}
size_t end2 = clock();
cout << t.IsBalanceTree() << endl;
cout << "AVLTree Insert:" << end2 - begin2 << endl;
cout << "AVLTree Height:" << t.Height() << endl;
cout << "AVLTree Size:" << t.Size() << endl;
size_t begin1 = clock();
// 确定在的值
/*for (auto e : v)
{
t.Find(e);
}*/
// 随机值
for (size_t i = 0; i < N; i++)
{
t.Find((rand() + i));
}
size_t end1 = clock();
cout << "AVLTree Find:" << end1 - begin1 << endl;
}
int main()
{
TestRBTree2();
TestAVLTree2();
return 0;
}
运行结果:
1 RBTree Insert:1202 RBTree Height:6 RBTree Size:22 RBTree Find:220 1 AVLTree Insert:2633 AVLTree Height:26 AVLTree Size:6325673 AVLTree Find:2447
结论:红黑树在插入/删除效率上优于AVL树,而AVL树在查找效率上略胜一筹。
本文阐述了红黑树是一种高效的自平衡二叉搜索树,通过颜色标记和旋转操作维持近似平衡,确保增删查改时间复杂度为O(log n)。其核心规则包括根节点黑、红节点子节点必黑、任意路径黑节点数相同,最长路径不超过最短两倍。相比AVL树,红黑树减少旋转次数,插入/删除效率更高,适合动态数据场景,广泛应用于C++ STL、Linux内核及数据库索引。实现时通过三叉链结构管理节点,插入需处理变色、单旋+变色、双旋+变色三种情况。验证时检查路径黑节点数与连续红色节点。测试表明,红黑树插入效率优于AVL树,而AVL树因严格平衡查找略快,两者均保障了稳定对数级性能。