1 .红黑树的概念
- 红黑树,是一种二叉搜索树。
- 在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。
- 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
2 .红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
上面的性质归根结底是三句话:
- 根节点是黑色的
- 没有连续的红节点
- 每条路径的黑节点数目相同(每条路径指的是从根节点到黑色的空节点)
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
最长路径: 该条路径上节点分布是一红一黑
最短路径: 该条路径上节点分布是全黑
- 根据红黑树的性质3可以得出,红黑树当中不会出现连续的红色结点,
- 而根据性质4又可以得出,从某一结点到其后代叶子结点的所有路径上包含的黑色结点的数目是相同的。
- 我们假设在红黑树中,从根到叶子的所有路径上包含的黑色结点的个数都是N个,那么最短路径就是全部由黑色结点构成的路径,即长度为N。
3 . 红黑树节点的定义
struct BRTreeNode
{
// 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
BRTreeNode<K, V>* _parent;
BRTreeNode<K, V>* _left; // 节点的左孩子
BRTreeNode<K, V>* _right; // 节点的右孩子
pair<K, V> _kv;// 节点的值
color _color;// 节点的颜色
//构造函数
BRTreeNode(const pair<K, V>& kv) :_kv(kv), _parent(nullptr), _left(nullptr), _right(nullptr),_color(red) {}
};
思考:在节点的定义中,为什么要将节点的默认颜色给成红色的?
- 插入黑色结点,一定破坏红黑树的性质4,必须对红黑树进行调整。
- 插入红色结点,可能破坏红黑树的性质3,可能对红黑树进行调整。
- 总之就是避免立即破坏树的平衡性。
4 . 红黑树的插入
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
- 先按照二叉搜索树树插入节点的方式,插入节点
- 为了不破坏红黑树的规则,我们插入节点后要对红黑树进行相应的调整(旋转等)
bool Insert(const pair<K, V>& kv)
{
//先按照二叉搜索树正常插入
if (!_head)
_head = new Node(kv);
else
{
Node* cur = _head;
Node* parent = nullptr;
while (cur)
{
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else return false;
}
//找到插入位置,开始插入
cur = new Node(kv);
if (kv.first < parent->_kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//插入完成判断父亲是否为黑 如果为黑则没有破环规则
if (parent->_color == black) return true;
//如果父亲是红色的,则需要更新
//更新颜色
while (parent && parent->_color == red)
{
//父亲存在且为红
//看叔叔是否存在
Node* grandparent = parent->_parent;
Node* uncle = grandparent->_left;
if (uncle == parent)
uncle = grandparent->_right;
if (uncle)
{
//叔叔存在且为红 变色即可
if (uncle->_color == red)
{
parent->_color = uncle->_color = black;
grandparent->_color = red;
cur = grandparent;
parent = cur->_parent;
}
//叔叔存在且为黑 旋转
else
{
if (cur == parent->_left)
{
// g p
// p u -> c g
// c u
if (parent == grandparent->_left)
{
RotateR(grandparent);
grandparent->_color = red;
parent->_color = black;
}
// g c
// u p -> g p
// c u
else
{
RotateRL(grandparent);
grandparent->_color = red;
cur->_color = black;
}
}
else
{
// g
// u p
// c
if (parent == grandparent->_right)
{
RotateL(grandparent);
grandparent->_color = red;
parent->_color = black;
}
// g
// p u
// c
else
{
RotateLR(grandparent);
grandparent->_color = red;
cur->_color = black;
}
}
break;
}
}
//叔叔不存在
else
{
if (cur == parent->_left)
{
if (parent == grandparent->_left)
{
RotateR(grandparent);
grandparent->_color = red;
parent->_color = black;
}
else
{
RotateRL(grandparent);
grandparent->_color = red;
cur->_color = black;
}
}
else
{
if (parent == grandparent->_left)
{
RotateLR(grandparent);
grandparent->_color = red;
cur->_color = black;
}
else
{
RotateL(grandparent);
grandparent->_color = red;
parent->_color = black;
}
}
break;
}
}
}
//经过一系列操作,把根节点变黑
_head->_color = black;
return true;
}
检测新节点插入后,红黑树的性质是否造到破坏
- 因为新节点的默认颜色是红色,
因此:
如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;
红黑树在插入结点后是如何调整的?
- 在插入结点后并不是一定会对红黑树进行调整,若插入结点的父结点是黑色的,那么我们就不用对红黑树进行调整,因为本次结点的插入并没有破坏红黑树的所有性质。
- 当插入结点的父结点是红色时才需要对红黑树进行调整,通过上面知道我们默认插入的结点就是红色的,如果插入结点的父结点也是红色的,那么此时就出现了连续的红色结点,因此需要对红黑树进行调整。
- 如果插入结点的父结点是红色的,说明父结点不是根结点(根结点是黑色的),因此插入结点的祖父结点就一定存在。
- 红黑树调整时具体应该如何调整,主要是看插入结点的叔叔结点
但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
- 情况一: cur为红,p为红,g为黑,u存在且为红
cur和p均为红,违反了性质三,此处能否将p直接改为黑?
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
- 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转p、g变色--p变黑,g变红
- 情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2
针对每种情况进行相应的处理即可。
//右旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppNode = parent->_parent;
parent->_parent = subL;
if (parent == _head)
{
_head = subL;
_head->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
}
//左旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* ppNode = parent->_parent;
parent->_parent = subR;
if (parent == _head)
{
_head = subR;
_head->_parent = nullptr;
}
else
{
if (ppNode->_right == parent)
{
ppNode->_right = subR;
}
else
{
ppNode->_left = subR;
}
subR->_parent = ppNode;
}
}
//先右后左
void RotateRL(Node* parent)
{
RotateR(parent->_right);
RotateL(parent);
}
//先左后右
void RotateLR(Node* parent)
{
RotateL(parent->_left);
RotateR(parent);
}
5 .红黑树的验证
红黑树的检测分为两步:
- 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
- 检测其是否满足红黑树的性质
bool IsValidRBTRee()
{
if (_head->_color == red)
return false;
int refnum = 0;
Node* cur = _head;
//计算一条路径下的黑色节点的数量
//用于与其他路劲做比较
while (cur)
{
if (cur->_color == black)
refnum++;
cur = cur->_left;
}
return _IsValidRBTRee(_head, 0, refnum);
}
bool _IsValidRBTRee(Node* head, int cnt, int num)
{
if (head == nullptr)
{
if (cnt != num)
return false;
return true;
}
//判断是否红色节点连号了
if (head->_color == red && head->_parent->_color == red)
{
cout << head->_kv.first << " : " << head->_kv.second << endl;
return false;
}
//cnt用来记录每条路上的黑色节点数量
if (head->_color == black)
cnt++;
return _IsValidRBTRee(head->_left, cnt, num);
}
6 .红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O($log_2 N$),红黑树不追
求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,
所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红
黑树更多。

总结: AVL树是严格意义上的平衡,红黑树是相对的平衡。两者都很高效。但后者用的更多。因为它旋转更少,实现相对简单,付出的代价少一点。