一. 介绍
红黑树是一种二叉搜索树的优化版本。其每个节点都有颜色标识,红或黑。
红黑树在二叉搜索树的基础上具有一下性质:
- 每个节点是黑色或者红色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子节点是黑色的(不能有两个连续的红色节点)
- 对于每一个节点,从该节点到其所有后代叶节点的路径上黑色节点数目相同
- 每一个叶子节点都是黑色的。空节点规定为叶子节点,为黑色
通过这些规则,可以保证一棵红黑树从根到叶子的最长可能路径不会超过最短可能路径的两倍。
从根节点到叶子节点,路径上的黑色节点都是4个
最长路径:1黑1红节点构成的路径;最短路径:全黑节点构成的路径
红黑树是近似平衡的,即接近完全二叉树。
二. 简单实现红黑树
1. 基本框架
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;//<key,value>键值对
Colour _col;//节点颜色
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)//新节点定义为红色
{}
};
template<class K, class V>
struct RBTree
{
typedef RBTreeNode<K, V> Node;
private:
Node* _root = nullptr;
}
新节点初始为红色
如果新节点是黑色,那么插入该节点的路径上黑色节点的数目就会比其他路径多1,破坏了规则4,就需要马上调整
如果新节点是红色,其父节点的颜色也为红色,破坏规则3,需要调整;其父节点是黑色的,不需要调整
2. 插入(Insert)
新(红色)节点的插入,可能会引起红黑树规则的破坏。
步骤:
-
按照搜索二叉树进行插入新节点
-
如果新节点的父节点是红色,则对红黑树进行调整
规则2:根节点是黑色的。
因此,新节点的父节点为红色,需要进行调整时,祖父节点一定存在,其叔叔节点也存在。
叔叔节点:父节点的兄弟节点
调整时,根据叔叔节点的颜色,分为2种情况。
cur:当前节点,p:父节点,g:祖父节点,u:叔叔节点
a. 叔叔节点为红色
调整图示:
步骤:
将p,u的颜色改为黑色
如果g为子树的根,g的颜色改为红色,并继续向上调整
如果g是根节点,则仍为黑色
b. 叔叔节点为黑色
规则5,u为黑色,u存在 或 为空节点
调整:下面4种调整中,未对u进行操作
- p为g的右孩子,cur为p的左孩子。左旋
-
p为g的左孩子,cur为p的右孩子。右旋
-
p为g的左孩子,cur为p的右孩子。左右双旋
-
p为g的右孩子,cur为p的左孩子。右左双旋
这4种情况,调整后子树的根节点都会为黑色,因此不需要继续向上调整了。
c. 代码
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;//cur的父节点
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);
if (kv.first > parent->_kv.first)//新节点的键值大于parent
{
parent->_right = cur;//放在parent的右边
}
else
{
parent->_left = cur;//左边
}
cur->_parent = parent;
// 调整
//(cur最差会遍历到根节点)p为红色
while (parent && parent->_col == RED)
{
Node* grandfater = parent->_parent;//parent的父节点
// 关键看叔叔
if (parent == grandfater->_left)//p是g的左孩子
{
Node* uncle = grandfater->_right;//叔叔节点
// 情况一 : u为红,
if (uncle && uncle->_col == RED)
{
// 变色
parent->_col = uncle->_col = BLACK;
grandfater->_col = RED;
// 继续往上处理
cur = grandfater;
parent = cur->_parent;
}
else// 情况二:u为黑
{
//情况2. c为p的左孩子
if (cur == parent->_left)
{
// 右单旋+变色
// g
// p u
// c
RotateR(grandfater);//右旋
parent->_col = BLACK;
grandfater->_col = RED;
}
else//情况3. c为p的右孩子
{
// 左右单旋+变色
// g
// p u
// c
RotateL(parent);//左旋
RotateR(grandfater);//右旋
cur->_col = BLACK;
grandfater->_col = RED;
}
break;//结束
}
}
else // (parent == grandfater->_right) //p是g的右孩子
{
Node* uncle = grandfater->_left;//叔叔节点
// 情况一 : u为红,
if (uncle && uncle->_col == RED)
{
// 变色
parent->_col = uncle->_col = BLACK;
grandfater->_col = RED;
// 继续往上处理
cur = grandfater;
parent = cur->_parent;
}
else//情况二 : u为黑,
{
//情况1. c为p的右孩子
if (cur == parent->_right)
{
// 左单旋+变色
// g
// u p
// c
RotateL(grandfater);//左旋
parent->_col = BLACK;
grandfater->_col = RED;
}
else// 情况4. c为p的左孩子
{
// 情况三:右左单旋+变色
// g
// u p
// c
RotateR(parent);//右旋
RotateL(grandfater);//左旋
cur->_col = BLACK;
grandfater->_col = RED;
}
break;//结束
}
}
}
_root->_col = BLACK;//根节点颜色为黑色(可能在情况一下被修改)
return true;
}
3. 验证红黑树
- 中序遍历检验是否有序
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
- 红黑树性质验证
bool IsRBTree()
{
if (_root == nullptr)
{
return true;
}
if (_root->_col == RED)
{
cout << "根节点不是黑色" << endl;
return false;
}
int benchmark = 0;// 黑色节点数量基准值
return PrevCheck(_root, 0, benchmark);
}
bool PrevCheck(Node* root, int blackNum, int& benchmark)
{
if (root == nullptr)//路径走完,到空节点
{
//benchark更新为第一条路径上黑色节点的数量
if (benchmark == 0)
{
benchmark = blackNum;
return true;
}
if (blackNum != benchmark)
{
cout << "某条路径黑色节点的数量不相等" << endl;
return false;
}
else
{
return true;
}
}
if (root->_col == BLACK)
{
++blackNum;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "存在连续的红色节点" << endl;
return false;
}
return PrevCheck(root->_left, blackNum, benchmark)
&& PrevCheck(root->_right, blackNum, benchmark);
}
示例:
void TestRBTree()
{
size_t N = 1000;
srand(time(0));
RBTree<int, int> t1;
int count = 0;
for (size_t i = 0; i < N; ++i)
{
int x = rand();
++count;
t1.Insert(make_pair(x, i));
}
cout << "插入结点:" << count << endl;
cout << "IsRBTree:" << t1.IsRBTree() << endl;
}
int main()
{
TestRBTree();
return 0;
}
三. 红黑树与AVL树
AVL树和红黑树都是搜索二叉树的概念版,其中AVL树出现的更早。
- 搜索时间复杂度:都是 O ( l o g 2 N ) O(log_2N) O(log2N)
AVL树:高度 H ≈ l o g 2 N H \approx log_2N H≈log2N, O ( H ) O(H) O(H)
红黑树:路径长度 L = [ H , 2 H ] L=[H,2H] L=[H,2H], O ( L ) O(L) O(L)
- AVL树通过控制平衡因子,较多的调整,更接近完全二叉树,更平衡
- 红黑树通过控制节点的颜色,较少的调整,最长路径不超过最短路径的2倍,近似平衡
红黑树相比于AVL树,降低了旋转调整的次数,而且实现较简单。因此实际应用红黑树更加广泛,如STL中map、set等都是红黑树作为底层结构。
🦀🦀观看~~