红黑树的性质
1.节点非红即黑。
2.根节点是黑色。
3.所有NULL结点称为叶子节点,且认为颜色为黑。
4.所有红节点的子节点都为黑色。
5.从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点。
一共五点性质,初看有点懵。待我来给你解释:
前面两点每什么好说的,从第三点开始:
3、此叶子结点非彼叶子结点,我觉得它这里把NULL认为是叶子结点,是为了更加清楚的看到每一条路径。
4、所有的红色结点的孩子结点为黑色结点;换句话说,在红黑树中不存在两个连续的红色结点
5、第五条性质我觉得是维持红黑树平衡的一条最重要的性质。由于第四条性质的限制,树中最长的那一条路径一定是红黑结点向间隔的;最短的路径就全都是黑结点。那么:最长路径 <= 2 * 最短路径。有这一点的限制,就很好的维持了红黑树的平衡了。
所有说在红黑树上搜索的最差时间复杂度为2 * O(logN),而AVL树的时间复杂度为O(logN),这一看来AVL树的效率比红黑树的效率更优,但是在实际中,使用红黑树却比AVL树的频率更高。这是因为现在硬件性能已经很好了,O(logN)这一时间复杂度已经很优了,就算是2倍还是很快。而且AVL树的平衡非常严格,需要旋转的次数比红黑树多。
红黑树结点的定义
cpp
enum Colour
{
BLACK,
RED,
};
template<class K, class V>
class RBTreeNode
{
RBTreeNode<K, v>* _left;
RBTreeNode<K, v>* _right;
RBTreeNode<K, v>* _parent;
pair<K, V> _kv;
Colour _col;
};
红黑树结点的插入
我们插入一个结点,默认该结点为红色,因为假如默认黑色,那么就会破坏第4点性质,插入了一个黑色结点,那么路径就会+1;如果默认红色,假如插入的结点的父结点是黑色,那么就不会破坏红黑树的性质。
红黑树结点的插入可分为两大步
- 按照二叉搜索树的规则插入结点
- 检测结点插入后,红黑树的性质是否被破坏
第一步在我之前的文章有记录,这里就不多复述了。
对于第二步,可分为三种情况:(令插入的红色结点为cur,p为插入结点的父结点,u为插入结点的叔叔结点,g为插入结点的祖父结点)
调整颜色主要是维护红黑树的这两个性质:没有连续的红色结点、子树中每条路径的黑色结点数量不变。
情况1:p为红,g为黑,u存在且为红
上图可以代表所有情况1
注意:这里的a,b,c,d,e,可能为空 ,这里的cur,可能是新增的结点,也可能是cur结点的子树通过变化,导致cur变成红色。但总得来说,只要符合该条件,都可以统一处理
处理方法:
如上图:p、u变红,g变黑, 但是这颗树可能只是子树,那么就要对g结点分情况讨论了:
1、当g结点为根结点,那么在调整完之后,将g改为黑色
2、当g结点是孩子结点,并且g的双亲如果是红色,那么就继续往上调整(cur = grandfather;
parent = cur->_parent;)
情况2:p为红,g为黑,u不存在 or u存在且为黑
1、当u不存在时,cur一定为新增结点。
这种情况下需要对g右单旋
2、当u存在并且为黑色时:
注意:这里的cur原本应该是黑色,因为保持路径黑色结点数量相同,u为黑色,所有cur应该是黑色。而现在cur是红色,是因为在cur的子树中,出现了情况1,进行了调整,也就是说在a,b两颗子树中,都至少存在一个黑色结点。
调整流程:无论是u存不存在,都不是简单的颜色变化,就能够维持红黑树的性质的,这时候就需要旋转处理了。
旋转方式:
- cur、p、g三结点形成一条左单边树,对g右单旋
- cur、p、g三结点形成一条右单边树,对g左单旋
旋转结束后,需要进行颜色调整:p变黑,g变红。
情况3:p为红,g为黑,u不存在 or u存在且为黑
可以发现情况三和情况二的颜色情况是一样的,其实情况三是情况二的差别不在结点的颜色上,差别在于结点的位置。情况二只是对于cur、p、g三结点位于单边树的情况进行分析,而接下来就要对三结点形成折线进行分析。
首先还是对于u是否存在进行讨论:
1、当u不存在:
和情况二是一样的,当u不存在时,cur一定为新增结点。
2、当u存在且为黑:
也是和情况二是一样的,cur也是在其子树中发生了情况一的颜色调整,才变为红色,原本应为黑色。
调整流程:其实我并没有将这个旋转流程画完,其实上面进行一次单旋后,可以发现和情况二的初始情况是一样的,只是p和cur两结点的位置变换了而已。
旋转方式:(这里不同于情况二,这三个结点形成一条折线)
- p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;
- p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
- 交换cur和p结点,变为情况二,一起处理。
插入整体代码实现
cpp
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;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
//新增的红结点
cur->_col = RED;
while (parent && parent->_col == RED)
{
//红黑树的调节关键看uncle结点
Node* grandfather = parent->_parent;
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
// 情况1:uncle存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上处理
cur = grandfather;
parent = cur->_parent; // 父亲可能不存在,所以在循环条件加上
// 有可能为根结点,但是为红,所以在最后暴力处理为黑
}
// uncle 不存在 or 为黑
else
{
// 情况三:双旋 -> 变成单旋
if (cur == parent->_right)
{
RotateL(parent);
swap(parent, cur); // 变成第二种情况
}
// 情况二(ps:有可能是第三种情况变过来的)
RotateR(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
break;
}
}
else
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
uncle->_col = parent->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
if (cur == parent->_left)
{
RotateR(parent);
swap(cur, parent);
}
RotateL(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
break;
}
}
}
_root->_col = BLACK; // 暴力处理根结点为黑色
return true;
}
void RotateL(Node* parent)
{
// 注意:链接关系一对一对去断开和连接,这样不容易混乱,更加有条理
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
//假如parent只是一颗子树的根结点,那么需要记录一下parent的父结点
Node* ppNode = parent->_parent;
parent->_parent = subR;
// 1、原来parent是这颗树的根,现在subR是根
//2、parent为根的树只是整颗树的子树,改变链接关系,subR顶替它的位置
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
subR->_parent = ppNode;
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
}
}
//右单旋
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 (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
subL->_parent = ppNode;
if (ppNode->_left == parent)
ppNode->_left = subL;
else
ppNode->_right = subL;
}
}