目录
前言
map与set作为STL中自带的搜索结构,其底层依托于红黑树而实现。本文将详细讲解红黑树的概念及其实现逻辑,其中部分基础逻辑(如旋转)可参考AVL树的实现。更多C++内容可前往>| C++专栏 |<
1.红黑树
1.1红黑树的概念
红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊。 通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍,因⽽是接近平衡的。
1.2红黑树的规则
1.每个节点不是红色就是黑色
2.根节点是黑色的
3.如果⼀个节点是红⾊的,则它的两个孩⼦节点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的红⾊节点。
4.对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的黑色节点



2.红黑树的实现
2.1红黑树的结构
红黑树整体还是搜索树的一种,但每个节点除了存储键值、左子节点和右子节点指针外,还需要额外存储一个颜色属性。这里的颜色属性可用枚举值来表示。
cpp
#pragma once
#include<iostream>
using namespace std;
enum Color
{
RED,
BLACK
};
template<class K, class V>
class RBTreeNode
{
pair<K, V> _kv;
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
Color _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;
};
2.2红黑树的插入
1.红黑树插入情况分析
1.大体插入按二叉搜索树的规则插入,插入完看是否符合红黑树的规则
2.当为空树插入时,新增节点为黑色节点;如果是非空树插入,新增节点必须是红色节点(若为黑色节点,则破坏规则4,更难处理)
3.⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是⿊⾊的,则没有违反任何规则,插⼊结束
4.⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是红⾊的,则违反规则3,此时须进行特殊处理
对情况4进行进一步分析:

例如在该树中插入一个新节点,其中cur表示新增节点,parent表示新增节点的父亲,grandpa表示新增节点的父亲的父亲,uncle表示parent的相邻节点。如何parent为黑则插入结束,如果parent为红则grandpa必定为黑,此时再将这棵树单独拿出来:

此时根据分析cur为红,parent为红,则grandpa必为黑,最终问题情况就要由uncle颜色来确定:
2.2.1变色
情况1:cur为红,parent为红,grandpa为黑,uncle存在且为红。
此时的情况就与上图的情况相同,这时新增节点为红色,那么只需要将parent节点与uncle节点都变为黑,grandpa变为红即可。
即将grandpa的黑色分散到两个子路径中,同时满足规则3与规则4。但要注意的是若此时grandpa为根节点则需将其再变为黑色;若不为根,则继续向上更新。

此为具体图分析,下面展示抽象图。

此时再回到更新后的树:

可以看到仍然存在违反规则3的情况,下面继续分析
情况2:cur为红,parent为红,grandpa为黑,uncle不存在或存在且为黑。
若uncle不存在则cur必为新增节点;若uncle存在且为黑则cur必不为新增节点(如上图),一定是通过变色变为红色。
此时必须要将parent变为黑才能解决连续红色的问题,但因uncle不存在或为黑,无法单纯通过变色解决问题,此时我们可以根据AVL树的旋转情况以grandpa为根进行旋转。
2.2.2单旋+变色
如果p是g的左,c是p的左,那么以g为旋转点进⾏右单旋,再把p变⿊,g变红即可。

如果p是g的右,c是p的右,那么以g为旋转点进⾏左单旋,再把p变⿊,g变红即可。
2.2.3双旋+变色
如果p是g的左,c是p的右,那么先以p为旋转点进⾏左单旋,再以g为旋转点进⾏右单旋,再把c变⿊,g变红即可。

如果p是g的右,c是p的左,那么先以p为旋转点进⾏右单旋,再以g为旋转点进⾏左单旋,再把c变⿊,g变红即可。
2.2.4插入代码
cpp
bool insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (kv.first < cur->_kv.first)
{
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
cur->_col = RED;
if (kv.first < parent->_kv.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
while (parent && parent->_col == RED)
{
Node* grandpa = parent->_parent;
if (parent == grandpa->_left)
{
Node* uncle = grandpa->_right;
//uncle存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandpa->_col = RED;
cur = grandpa;
parent = cur->_parent;
}
//uncle不存在或uncle存在且为黑
else
{
if (cur == parent->_left)
{
rotateR(grandpa);
parent->_col = BLACK;
grandpa->_col = RED;
}
else
{
rotateLR(grandpa);
parent->_col = BLACK;
grandpa->_col = RED;
}
}
}
else
{
Node* uncle = grandpa->_left;
//uncle存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandpa->_col = RED;
cur = grandpa;
parent = cur->_parent;
}
//uncle不存在或uncle存在且为黑
else
{
if (cur == parent->_right)
{
rotateL(grandpa);
parent->_col = BLACK;
grandpa->_col = RED;
}
else
{
rotateRL(grandpa);
parent->_col = BLACK;
grandpa->_col = RED;
}
}
}
}
_root->_col = BLACK;//确保根节点一直为黑
return true;
}
2.3红黑树的查找
红黑树查找逻辑与二叉搜索树查找逻辑相同:
cpp
//查找
Node* find(const pair<K, V>& x)
{
Node* cur = _root;
while (cur)
{
if (x.first < cur->_kv.first)
{
cur = cur->_left;
}
else if (x.first > cur->_kv.first)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return nullptr;
}
2.4红黑树的验证
这⾥获取最⻓路径和最短路径,检查最⻓路径不超过最短路径的2倍是不可⾏的,因为就算满⾜这个条件,红⿊树也可能颜⾊不满⾜规则,当前暂时没出问题,后续继续插⼊还是会出问题的。所以我们还是去检查4点规则,满⾜这4点规则,⼀定能保证最⻓路径不超过最短路径的2倍。
红黑树4点规则检查
规则1枚举颜⾊类型,天然实现保证了颜⾊不是⿊⾊就是红⾊。
规则2直接检查根即可
规则3前序遍历检查,遇到红⾊结点查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲的颜⾊就⽅便多了。
规则4前序遍历,遍历过程中⽤形参记录跟到当前结点的blackNum(⿊⾊结点数量),前序遍历遇到⿊⾊结点就++blackNum,⾛到空就计算出了⼀条路径的⿊⾊结点数量。任意⼀条路径⿊⾊结点数量作为参考值,依次⽐较即可。
代码如下:
cpp
bool Check(Node* cur, int blackNum, const int blackNumRef)
{
if (cur == nullptr)
{
if (blackNum != blackNumRef)
{
cout << "黑色节点的数量不相等" << endl;
return false;
}
return true;
}
if (cur->_col == RED && cur->_parent && cur->_parent->_col == RED)
{
cout << cur->_kv.first << "->" << "连续的红色节点" << endl;
return false;
}
if (cur->_col == BLACK)
++blackNum;
return Check(cur->_left, blackNum, blackNumRef)
&& Check(cur->_right, blackNum, blackNumRef);
}
//验证
bool isBalance()
{
if (_root == nullptr)
return true;
if (_root->_col == RED)
return false;
// 黑色节点数量参考值
Node* leftMost = _root;
int blackRef = 0;
while (leftMost)
{
if (leftMost->_col == BLACK)
++blackRef;
leftMost = leftMost->_left;
}
return Check(_root, 0, blackRef);
}
3.测试代码
cpp
#include"RBTree.h"
#include<iostream>
using namespace std;
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 });
//t.InOrder();
//cout << "Insert:" << e << "->" << t.IsBalanceTree() << endl;
}
t.inOrder();
cout << t.isBalance() << endl;
}
int main()
{
TestRBTree1();
return 0;
}