红黑树
红黑树是一种自平衡的二叉查找树,通过颜色规则维持平衡,避免二叉查找树退化成链表,保证插入、删除、查找的效率。
它的基本特性有:
每个节点要么是红色,要么是黑色。 根节点一定是黑色。 叶子节点(空节点)是黑色。 若一个节点是红色,它的两个子节点必是黑色(不会有两个红节点相连)。 从任意节点到其所有叶子节点的路径中,黑色节点的数量相同(黑高一致)。 最长路劲的结点个数不会超过最短路径的2倍(但是不一定会有最短,也不一定是会有最长)。大部分路径长度在这个区间h<=红黑树中任意一条路径长度<=2h 红黑树是近似平衡,没有像AVL树那样的严格平衡。
红黑树相比AVL树的优势
红黑树相比AVL树的核心优势是降低了插入和删除操作的旋转次数,从而在动态数据频繁变动的场景下拥有更优的性能。
具体优势体现在以下两点:
-
旋转操作更少:AVL树要求任意节点的左右子树高度差不超过1(平衡因子严格),这导致插入/删除时为恢复平衡可能需要多次旋转;而红黑树通过“红黑节点”的颜色规则(如根黑、叶黑、相邻节点不双红等)间接维持平衡,对高度差的限制更宽松,通常只需1-2次旋转即可恢复平衡。
-
动态场景更高效:在数据频繁插入、删除的场景(如高频更新的集合、数据库索引)中,红黑树因旋转开销低,整体执行效率优于AVL树,是工程实践中更常用的平衡树结构(如Java的TreeMap、C++的map均基于红黑树实现)。
红黑树我们优先插入红色结点,因为插入黑色结点会导致的变化太多(如违背每条路径黑色结点相等这一规则) ,相对更不可控
插入之后的旋转逻辑(插入之后的处理)
红黑树的处理关键看叔叔,因为其他结点的颜色情况基本相同
以下的情况主要用抽象的方式展现,每种情况中子树都有可能有多种排列组合所以每种情况实际涵盖了大量类似的情况,而他们的处理方式一致
还有一点需要注意且比较难以理解的地方是,cur并不一定是新增,也有可能是下面结点往上调整的结果,所以可能存在cur有子节点的情况
情况1:cur(插入节点)为红,g(爷爷结点)为黑,u(叔叔结点)存在且为红(仅仅需要变色)
解决方式,将p,u改为黑,(当g非根节点时)g改为红 然后,g变成cur继续向上调整
g一定也要改为红的原因是,若不更改,对于爷爷往上的结点的路径都会凭空多出一个黑色节点,会影响到其他部分,所以改红是为了保持黑色节点的不变
接着向上调整,此时g为cur了,判断cur的父亲结点是否为红 为红。 继续向上处理 不为红。 处理结束

第一种情况的场景一:
a,b,c,d,e都为空,cur是新增
场景二:

cde可能是下面四种子树x/y/z/r的任意一种(每条路径仅有一个黑色结点的红黑树)

新增节点插入在ab两个节点--他们的四个孩子节点中的任意一个位置,都将会引发更新(需要变色和向上更新)
这里cde的情况组合有4* 4 *4=64种,而插入位置(ab的左右孩子)有4种,所以包含了至少256种情况。这也是用抽象方式展现的原因,所有的情况会画不完。
情况二:(涉及旋转+变色)
cur为红,p为红,g为黑,u不存在/u存在且为黑
场景一:u不存在,则abcde都为空,cur是新增
场景二:其中u存在且为黑,它一定会是从下面往上更新变色导致的

c为一个黑色结点的红黑树x/y/z/r中的其中一种,而de为空或者一个红色节点
以下为手撕红黑树部分功能的源代码,可以参考上文说明以及注释理解
#include<iostream>
using namespace std;
#pragma once
enum Color {
RED,
BLACK
};
template<class K,class V>
struct RBTreeNode {
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Color _col;
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
,_col(RED)///////
{}
};
template<class K,class V>
class RBTree {
typedef RBTreeNode<K, V> Node;
public:
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;
}//要插入结点大于当前节点key值,向右寻找插入位置
else if (cur->_kv.first > kv.first) {
parent = cur;
cur = cur->_left;
}//当然,这是小于的情况
else {
return false;
}//相等key值,红黑树不能插入
}//出来以后cur此时为空,,且为符合要求的插入位置
cur = new Node(kv);
cur->_col = RED;
if (parent->_kv.first < kv.first) {
parent->_right = cur;
}
else {
parent->_left = cur;
}//判断新插入的结点到底是父亲的右节点还是左节点,符合AVL树的要求
cur->_parent = parent;
while (parent && parent->_col == RED) {
//这里的if语句结构是,先分成cur是父亲左结点还是右节点一层,然后在这一层内又分为两种情况
//判断cur的父亲是否为红进入相应的处理情况,与博客中所写共两种情况,每种情况两个场景一一对应
//同时while语句的应用使我们可以不断向上处理
Node* grandfather = parent->_parent;
if (parent == grandfather->_left) {
//g
//p u
Node* uncle = grandfather->_right;
//叔叔存在且为红
if (uncle && uncle->_col == RED) {
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;//这里不论爷爷是否为根节点都先变成红色,后面再额外处理如果是根节点的情况
//继续往上
cur = grandfather;
parent = cur->_parent;
}//以上为情况一,包含场景一二一同处理
else {
//叔叔不存在或者存在且为黑
//g
//p u
//c
if (cur == parent->_left) {
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else {
//g
//p u
// c
RotateL(parent);
RotateR(grandfather);
grandfather->_col = RED;
cur->_col = BLACK;
}
break;
}
}
else {//父亲是爷爷的右边结点的情况
//g
//u p
Node* uncle = grandfather->_left;
//叔叔存在且红,和上面一样,仅需变色
if (uncle && uncle->_col == RED) {
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续往上
cur = grandfather;
parent = cur->_parent;
}
else//叔叔不存在或存在但为黑
{
//g
//u p
// c
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;//全处理完之后,单独把根节点置黑,防止违反规则
return true;
}
void RotateR(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
subL->_right = parent;
if (subLR)
subLR->_parent = subL;
Node* parentParent = parent->_parent;
parent->_parent = subL;
if (parent == _root) {
_root = subL;
subL->_parent = nullptr;
}
else {
if (parentParent->_left == parent) {
parentParent->_left = subL;
}
else {
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
}
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;
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else {
if (parentParent->_left == parent) {
parentParent->_left = subR;
}//本来父亲是上个结点的左节点,那么左旋之后现在subR是父亲,它也还是爷爷结点的左节点
else {
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
}
void InOrder() {
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root) {
if (root == nullptr) {
return;
}
_InOrder(root->left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
bool Check(Node* root, int blackNum, const int refNum) {
if (root == nullptr) {
if (refNum != blackNum) {
cout << "存在黑色结点的数量不相等的路径" << endl;
return false;
}
return true;
}
if (root->_col == RED && root->_parent && 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 IsBalanceTree() {
if (_root ==nullptr)
return true;
if (_root->_col != BLACK) {
return false;//额外处理根节点不是黑色,和空树两种情况,与上文不论g是否为根节点都变红的代码相呼应
}
int refNum = 0;
Node* cur = _root;
while (cur) {
if (cur->_col == BLACK) {
++refNum;
}
cur = cur->_left;
}
return Check(_root, 0, refNum);
}
private:
Node* _root = nullptr;
};

2212

被折叠的 条评论
为什么被折叠?



