红黑树的性质
1.节点非红即黑。
2.根节点是黑色。
3.所有NULL结点称为叶子节点,且认为颜色为黑。
4.所有红节点的子节点都为黑色。
5.从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点。
一共五点性质,初看有点懵。待我来给你解释:
前面两点每什么好说的,从第三点开始:
3、此叶子结点非彼叶子结点,我觉得它这里把NULL认为是叶子结点,是为了更加清楚的看到每一条路径。
4、所有的红色结点的孩子结点为黑色结点;换句话说,在红黑树中不存在两个连续的红色结点
5、第五条性质我觉得是维持红黑树平衡的一条最重要的性质。由于第四条性质的限制,树中最长的那一条路径一定是红黑结点向间隔的;最短的路径就全都是黑结点。那么:最长路径 <= 2 * 最短路径。有这一点的限制,就很好的维持了红黑树的平衡了。
所有说在红黑树上搜索的最差时间复杂度为2 * O(logN),而AVL树的时间复杂度为O(logN),这一看来AVL树的效率比红黑树的效率更优,但是在实际中,使用红黑树却比AVL树的频率更高。这是因为现在硬件性能已经很好了,O(logN)这一时间复杂度已经很优了,就算是2倍还是很快。而且AVL树的平衡非常严格,需要旋转的次数比红黑树多。
红黑树结点的定义
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为插入结点的祖父结点)
调整颜色主要是维护红黑树的这两个性质:没有连续的红色结点、子树中每条路径的黑色结点数量不变。
u存在且为红 | p、u变黑;g变红 |
u不存在 or 存在且为黑 | LR(RotateL(p),交换cur和p,RotateR(g));LL(RotateR(g)); RL(RotateR(p),交换cur和p,RotateL(g));RR(RotateL(g)) 全部都是g变红,p变黑 |
插入的结点为红,那么只会破坏性质四,不存在两个连续红结点。那么当父结点为黑时,就直接插入即可。当需要进行平衡操作时,一定是父结点为红,那么父结点一定不为根,那么就一定存在g,且g为黑色。
情况1: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结点,变为情况二,一起处理。
插入整体代码实现
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;
}
}
红黑树的验证
红黑树的验证分为两步:
- 中序遍历是否有序
- 检测其是否满足红黑树的性质
先使用循环,获取一条路径上黑色结点的数量,然后不断递归,当遇到为空结点时,代表该条路径走完了,进行黑色结点比较
bool IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col != BLACK)
return false;
Node* cur = _root;
int blackCount = 0;
while (cur)
{
if (cur->_col == BLACK)
blackCount++;
cur = cur->_left;
}
int count = 0;
return _IsBalance(_root, count, blackCount);
}
bool _IsBalance(Node* root, int count, int blackCount)
{
if (root == nullptr)
{
if (count != blackCount)
{
return false;
}
return true;
}
if (root->_parent && root->_col == RED && root->_parent->_col == RED)
{
return false;
}
if (root->_col == BLACK)
count++;
return _IsBalance(root->_left, count, blackCount) && _IsBalance(root->_right, count, blackCount);
}
红黑树的删除
删除红结点不会破坏性质,删除根结点,直接删,再更新根为黑即可。
删除结点最多存在一个结点,那么这个结点肯定是红色,直接用孩子结点顶替删除结点即可
删除结点无孩子时,最为复杂:这时需要考虑b,设n为b的孩子结点(nephew侄子)
b为红 | p一定为黑,p和b变色,超删除结点的方向单旋p,向上调整 |
b为黑,b有孩子结点 | 对于p和b的位置有:LL(右旋p),RR(左旋(p)) n变色b,b变色p,p变黑 LR(左旋b,右旋p),RL(右旋b,左旋p)n变p,p变黑 不需要向上调整 |
b为黑,b无孩子结点 | 当p为根结点 or p为红色结点时,b变红,p变黑,结束 当p为非根非红的普通结点时,b变红,继续向上调整 |
bool Erase(const pair<K, V>& kv)
{
Node* cur = _root;
Node* parent = nullptr;
int flag = 0; // 标记是否有删除的结点
// 寻找需要删除的结点
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
{
flag = 1;
if (cur->_left == nullptr)
{
// 假如要删除的是根结点,只要至少有一边为空,那么可以直接删
// 删完之后,直接拿不为空的那一边顶替根结点即可,再把颜色变为黑
if (_root == cur)
{
_root = _root->_right;
if (_root)
{
_root->_parent = nullptr;
_root->_col = BLACK;
}
delete cur;
return true;
}
break;
}
else if(cur->_right == nullptr)
{
if (_root == cur)
{
_root = _root->_left;
if (_root)
{
_root->_parent = nullptr;
_root->_col = BLACK;
}
delete cur;
return true;
}
break;
}
// 替代法
// 去右子树寻找最左结点,替代要删除的结点
else
{
Node* rightMin = cur->_right;
Node* rightMinParent = cur;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
// 找到了要替代的结点
// 进行替换
cur->_kv.first = rightMin->_kv.first;
cur->_kv.second = rightMin->_kv.second;
cur = rightMin;
parent = rightMinParent;
break;
}
// 先不删
}
}
// 没有要删除的结点
if (flag == 0)
return false;
Node* deleteNode = cur;
Node* deleteNodeParent = parent;
// 进行平衡
//此时cur为需要删除的结点,具有的特殊性质
//cur最多只有一个孩子结点
if (cur->_left != nullptr)
cur->_left->_col = BLACK;
else if (cur->_right != nullptr)
cur->_right->_col = BLACK;
// cur没有孩子结点
else
{
while (cur != _root && cur->_col != RED)
{
Node* brother = nullptr;
// cur为左孩子
if (cur == parent->_left)
brother = parent->_right;
// cur 为右孩子
else
brother = parent->_left;
// brother为黑色结点
if (brother->_col == BLACK)
{
//brother有孩子结点
if (brother->_left != nullptr || brother->_right != nullptr)
{
// RR
if (brother->_right != nullptr && brother == parent->_right)
{
RotateL(parent);
brother->_right->_col = brother->_col;
brother->_col = parent->_col;
parent->_col = BLACK;
}
//RL
else if (brother->_left != nullptr && brother == parent->_right)
{
brother->_left->_col = parent->_col;
parent->_col = BLACK;
RotateR(brother);
RotateL(parent);
}
// LL
else if (brother->_left != nullptr && brother == parent->_left)
{
brother->_right->_col = brother->_col;
brother->_col = parent->_col;
parent->_col = BLACK;
RotateR(parent);
}
// LR
else if (brother->_right != nullptr && brother == parent->_left)
{
brother->_right->_col = parent->_col;
parent->_col = BLACK;
RotateL(brother);
RotateR(parent);
}
break;
}
// brother无孩子结点
else
{
//当parent为根 or parent为红色结点
if (parent == _root || parent->_col == RED)
{
brother->_col = RED;
parent->_col = BLACK;
break;
}
//parent为非根普通黑结点
else {
brother->_col = RED;
cur = parent;
parent = parent->_parent; // 继续向上调整
}
}
}
// brother为红色结点
else
{
parent->_col = RED;
brother->_col = BLACK;
if (cur == parent->_left)
{
RotateL(parent);
//brother = parent->_right;
}
else
{
RotateR(parent);
//brother = parent->_left;
}
}
}
}
if (deleteNode->_left == nullptr) //实际删除结点的左子树为空
{
if (deleteNode == deleteNodeParent->_left) //实际删除结点是其父结点的左孩子
{
deleteNodeParent->_left = deleteNode->_right;
if (deleteNode->_right)
deleteNode->_right->_parent = deleteNodeParent;
}
else //实际删除结点是其父结点的右孩子
{
deleteNodeParent->_right = deleteNode->_right;
if (deleteNode->_right)
deleteNode->_right->_parent = deleteNodeParent;
}
}
else //实际删除结点的右子树为空
{
if (deleteNode == deleteNodeParent->_left) //实际删除结点是其父结点的左孩子
{
deleteNodeParent->_left = deleteNode->_left;
if (deleteNode->_left)
deleteNode->_left->_parent = deleteNodeParent;
}
else //实际删除结点是其父结点的右孩子
{
deleteNodeParent->_right = deleteNode->_left;
if (deleteNode->_left)
deleteNode->_left->_parent = deleteNodeParent;
}
}
delete deleteNode; //实际删除结点
return true;
}