一. 概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
二. 性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
由于从一个节点起的每条路径中的黑色节点数目相同,因此最短为全黑,同时由于红节点不连续,因此最长为一半红一半黑,因此不会超出两倍
三. 节点定义
三叉链,key-value模型、额外的标明颜色的变量(颜色可以使用枚举常量)
同时,为了在插入时遵循第4条准则,我们将颜色变量的默认值设为红色
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;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED)
, _kv(kv)
{}
};
四. 基本框架
template<class K, class V>
struct RBTree
{
typedef RBTreeNode<K, V> Node;
public:
RBTree()
:_root(nullptr)
{}
private:
Node* _root;
};
五. 插入
首先,一开始与AVL树是一致的,都是通过比较key来寻找合适的位置进行插入
bool Insert(const pair<K, V>& kv)
{
if (!_root)
{
_root = new Node(kv);
_root->_col = BLACK;
return false;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first == kv.first)
return false;
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
parent = cur;
cur = cur->_left;
}
}
cur = new Node(kv);
if (parent->_kv.first < kv.first)
parent->_right = cur;
else
parent->_left = cur;
cur->_parent = parent;
}
之后,虽然我们插入红色节点会使得每条路径的黑节点数目相同,但还要注意在红节点连续时进行相应的调整
在调整时,依旧是从插入节点(cur)向上迭代来观察,而我们需要进行调整的情况为cur和parent的颜色都为红,主要分为这两种情况
同样,和AVL树一样,也有对称的情况,但操作方法也都是类似的,不多说了
而这两种情况,我们对子树4的情况进行细分,首先当节点1的右节点为红色时
处理方法都是一样的,只需要将节点2、4的颜色改为黑色,同时,我们在改变结构时,为了减小对上面的影响,应该保证前后的路径中黑节点的数目相同,改色之前,黑节点数目为n+1,因此,我们还需要将节点1改为红色
而由于我们将节点1的颜色改为了红色,我们需要继续向上判断
while (parent&&parent->_col == RED)
{
Node* par_parent = parent->_parent;
Node* uncle = nullptr;
if (parent == par_parent->_left)
uncle = par_parent->_right;
else
uncle = par_parent->_left;
if (uncle && uncle->_col == RED)
{
uncle->_col = BLACK;
parent->_col = BLACK;
par_parent->_col = RED;
cur = par_parent;
parent = cur->_parent;
}
}
还有两种情况,是节点4不存在或节点4为黑色,这两种情况的处理情况相同,由于不需要对节点4进行处理,我们就简化一下
首先是情况1
首先若是我们想要改变颜色,首先对于节点2,右侧路径黑节点为n个,因此左侧也应该为n个,所以节点3的颜色不能改变,因此我们只能改变节点2,这样对于节点1,两侧路径的黑节点数目就不可能相同了,因此,我们可以使用在AVL树中使用到的旋转,很容易看出,我们需要使用右旋(对称情况使用左旋)
之后,便可以来改变颜色,首先,有两种方案,节点3改为黑色或节点1改为红色、节点2改为黑色,原本路径中黑节点数为n+1,这两种方案改变后都符合,但由于若是我们将节点2颜色改为黑色,与改变前类似,也就不需要继续向上判断了,因此采用第二种方法
else
{
if (cur == parent->_left && parent == par_parent->_left)
{
RotateR(par_parent);
parent->_col = BLACK;
par_parent->_col = RED;
}
else if (cur == parent->_right && parent == par_parent->_right)
{
RotateL(par_parent);
parent->_col = BLACK;
par_parent->_col = RED;
}
break;
}
而左旋和右旋和AVL树是一样的
void RotateL(Node* parent)
{
Node* par_parent = parent->_parent;
Node* right = parent->_right;
Node* right_left = right->_left;
if (par_parent)
{
if (parent == par_parent->_left)
par_parent->_left = right;
else
par_parent->_right = right;
}
else
{
_root = right;
}
right->_parent = par_parent;
if (right_left)
right_left->_parent = parent;
parent->_right = right_left;
right->_left = parent;
parent->_parent = right;
}
void RotateR(Node* parent)
{
Node* par_parent = parent->_parent;
Node* left = parent->_left;
Node* left_right = left->_right;
if (par_parent)
{
if (parent == par_parent->_left)
par_parent->_left = left;
else
par_parent->_right = left;
}
else
{
_root = left;
}
left->_parent = par_parent;
if (left_right)
left_right->_parent = parent;
parent->_left = left_right;
left->_right = parent;
parent->_parent = left;
}
之后便是情况2
同样只是改变颜色无法实现平衡
我们就需要进行双旋
之后就需要改变颜色
还是将节点3变黑,节点1变红,不需要向上调整
else
{
if (cur == parent->_left && parent == par_parent->_left)
{
RotateR(par_parent);
parent->_col = BLACK;
par_parent->_col = RED;
}
else if (cur == parent->_left && parent == par_parent->_right)
{
RotateR(parent);
RotateL(par_parent);
cur->_col = BLACK;
par_parent->_col = RED;
}
else if (cur == parent->_right && parent == par_parent->_right)
{
RotateL(par_parent);
parent->_col = BLACK;
par_parent->_col = RED;
}
else
{
RotateL(parent);
RotateR(par_parent);
cur->_col = BLACK;
par_parent->_col = RED;
}
break;
}
而在最后,头结点可能会被我们改变为红色,我们只需要将其手动变为黑就好了,不会对下面产生影响
总览
bool Insert(const pair<K, V>& kv)
{
if (!_root)
{
_root = new Node(kv);
_root->_col = BLACK;
return false;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first == kv.first)
return false;
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
parent = cur;
cur = cur->_left;
}
}
cur = new Node(kv);
if (parent->_kv.first < kv.first)
parent->_right = cur;
else
parent->_left = cur;
cur->_parent = parent;
while (parent&&parent->_col == RED)
{
Node* par_parent = parent->_parent;
Node* uncle = nullptr;
if (parent == par_parent->_left)
uncle = par_parent->_right;
else
uncle = par_parent->_left;
if (uncle && uncle->_col == RED)
{
uncle->_col = BLACK;
parent->_col = BLACK;
par_parent->_col = RED;
cur = par_parent;
parent = cur->_parent;
}
else
{
if (cur == parent->_left && parent == par_parent->_left)
{
RotateR(par_parent);
parent->_col = BLACK;
par_parent->_col = RED;
}
else if (cur == parent->_left && parent == par_parent->_right)
{
RotateR(parent);
RotateL(par_parent);
cur->_col = BLACK;
par_parent->_col = RED;
}
else if (cur == parent->_right && parent == par_parent->_right)
{
RotateL(par_parent);
parent->_col = BLACK;
par_parent->_col = RED;
}
else
{
RotateL(parent);
RotateR(par_parent);
cur->_col = BLACK;
par_parent->_col = RED;
}
break;
}
}
_root->_col = BLACK;
return true;
}
void RotateL(Node* parent)
{
Node* par_parent = parent->_parent;
Node* right = parent->_right;
Node* right_left = right->_left;
if (par_parent)
{
if (parent == par_parent->_left)
par_parent->_left = right;
else
par_parent->_right = right;
}
else
{
_root = right;
}
right->_parent = par_parent;
if (right_left)
right_left->_parent = parent;
parent->_right = right_left;
right->_left = parent;
parent->_parent = right;
}
void RotateR(Node* parent)
{
Node* par_parent = parent->_parent;
Node* left = parent->_left;
Node* left_right = left->_right;
if (par_parent)
{
if (parent == par_parent->_left)
par_parent->_left = left;
else
par_parent->_right = left;
}
else
{
_root = left;
}
left->_parent = par_parent;
if (left_right)
left_right->_parent = parent;
parent->_left = left_right;
left->_right = parent;
parent->_parent = left;
}
六. 验证
首先就是验证是否为搜索树,和AVL树的一致
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == NULL)
return;
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second <<':' << root->_col << " ";
_InOrder(root->_right);
}
之后,还需要验证三个问题:根节点是否为黑,路径中的黑节点数目是否相同、红节点是否不连续
首先第一点很好验证,若是验证第二点,我们可以先得到一条路径的黑节点,之后以它为基准,对每条路径进行判断,最后在遍历路径的时候顺便对红节点是否连续做一下判断
bool IsBalance()
{
if (_root && _root->_col == RED)
{
cout << "根节点不是黑色" << endl;
return false;
}
int banchmark = 0;
Node* left = _root;
while (left)
{
if (left->_col == BLACK)
banchmark++;
left = left->_left;
}
int blackNum = 0;
return _IsBalance(_root, banchmark, blackNum);
}
bool _IsBalance(Node* root, int& banchmark, int blackNum)
{
if (!root)
{
if (banchmark != blackNum)
{
cout << "存在路径黑色节点的数量不相等" << endl;
return false;
}
return true;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "出现连续红色节点" << endl;
return false;
}
if (root->_col == BLACK)
{
++blackNum;
}
return _IsBalance(root->_left, banchmark, blackNum)
&& _IsBalance(root->_right, banchmark, blackNum);
}
end