终于来到了期待已久的红黑树教学环境,相信大家已经期待已久了吧。大名鼎鼎的红黑树在我最早学习编程的时候就已听说过,当然网上虽然说这块内容难度很高,但是看过我之前文章的一些朋友们面对这一块内容的话应该不会那么艰难,当然没看过也别怕,实际上没大家想象的那么困难。ok,那么我们闲话少扯直接上正文。
1.基本概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
重点是其需要满足5个性质:
-
每个结点不是红色就是黑色
-
根节点是黑色的
-
如果一个节点是红色的,则它的两个孩子结点是黑色的
-
对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
-
每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
上图解释了红黑树确保没有一条路径会比其他路径长出俩倍
2.红黑树的基本结构和最为重要的检查
2.1新增头结点
为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,如图所示:
ok那么既然已经了解了最为基础的结构问题我们就快进到今天的重点问题:
2.2红黑树新节点插入后的检测以及修改机制
1.情况一:头一黑下三红
这里非常巧妙的用颜色变换的方式来让整个性质保持不变,不过要注意的是如果不是根结点且上面的结点为红的话需要上查并修改。
2.情况二:一黑,一红一黑加右红
其实像这种情况乍一看什么都没有改变,确实是这样,但是我们情况二的目的就是为了转换成我们的情况三
3.情况三:一黑,一红一黑一左红
这情况三巧妙的把情况二转换过来的模型改变颜色并右旋使规则未发生改变。
4.最为简单的情况就是插入之前上面的结点就为黑色
首先据上面观察我们插入的结点一律为红色,如果恰巧遇到父结点为黑色直接插入即可,不会影响规则。
3.红黑树最为复杂的删除
(注意此部分学有余力的同学可以观看,相对来说比较复杂)
首先在我们聊删除之前,我们不妨会议一下二叉搜索树的删除(如果忘记的同学可以看我之前写的文章有详细的说明)
1.删红色结点
假设一开始我们删除的就是红色结点,其实根本就不用管,对照那5条规则来看删除一个红色结点根本不会改变任何规则,所以此时只要遵从二叉搜索树的规则就行。
2.删除一个黑结点,且此结点下左右子树都不为空
通过二叉搜索树的规则,如果要删除这个结点,那么最终就会变成我们的第三种情况。(如果是红结点的话是第一种情况)
3.删除一个黑结点,且此结点下没有结点,或者只有一个结点。
这种情况最为麻烦,此处我们删除做了一个特殊处理。
如果删除的黑结点下面没有其他结点(这里空结点不算,写空结点从某种意义上来说是为了方便表示),那么我会在其上一个结点加上这个结点的黑色,也就是说,我让它上面那一个结点当成两个结点用**(一结点有两颜色)**
如果删除的黑结点下面有一个儿子,那么重复上述操作,然后在通过删除二叉搜索树的规则使儿子结点与那个删除的父结点相连接。
4.如何将一个结点两种颜色的结点恢复原样(变成单色)
红加黑
其实这种情况直接将该结点的红去掉变成单黑就行,无任何区别
黑+黑的4种情况:
注意此图片为双黑结点
注意此图片表示这个点可黑可红
1.第一种情况
2.第二种情况
3.第三种情况
这第三种情况存在的目的是为了转换成第四种情况。(让从上往下第一个黑结点的右儿子变成红色)
4.第四种情况
至此删除完成,这几种情况通过一一比对可以发现是满足了所有可能性的,因为删除代码过于繁琐,下面只分析插入的代码。
4.红黑树代码实现(一部分)
1.枚举(颜色处理)
enum color
{
RED,
BLACK
};
2.基础结点的创建
template<class T>
struct RBTreeNode
{shu
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
Colour _col;
RBTreeNode(const T& data)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_cool(RED)
,_data(data)
{}
};
3.树的基本结构
template<class K, class T, class KeyOfT>
struct RBTree
{
typedef RBTreeNode<T> Node;
public:
typedef RBTreeIterator<T, T&, T*> iterator;
iterator begin()
{
Node* min = _root;
while (min && min->_left)
{
min = min->_left;
}
return iterator(min);
}
iterator end()
{
return iterator(nullptr);
}
RBTree()
:_root(nullptr)
{}
private:
Node* _root;
};
首先之后我们因为要实现map和set,那它们的底层就是红黑树所有会有一个遍历,其迭代器的写法请参考列表,这里就不一一写出了(虽然从逻辑来说存在差异但是大框架是不变的)
4.比较复杂的插入
bool Insert(const T& data)
{
if(_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return true;
}
KeyOfT kot;
Node* cur = _root;
Node* parent = nullptr;
while(cur)
{
if (kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_right;
}
else if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(data);
cur->_col = RED; // 新增节点(注意一直是红色我插入的结点)
if (kot(parent->_data) < kot(data))
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//上述代码运行完时代表结点已经插入完毕
//下面开始控制平衡
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
// 1、uncle存在且为红
if (uncle && uncle->_col == RED)
{
// 变色+继续向上处理
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else // 2 + 3、uncle不存在/ 存在且为黑
{
// g
// p
// c
// g
// p
// c
if (cur == parent->_left)
{
// 单旋
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// 双旋
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else // parent == grandfather->_right
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
// 变色+继续向上处理
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else // 2 + 3、uncle不存在/ 存在且为黑
{
// g
// p
// c
// g
// p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}