文章目录
前言:个人观点,所谓手撕红黑树插入,就是记性好而已,理解并背下框架即可。插入代码量在200行左右
红黑树特征
- root是黑色的
- 若一个节点是红色的,则它两个孩子是黑色的。(没有连续的红节点)
ps:黑色节点可以连续 - 每条路径的黑色节点数量相等(一条路径指从root到某一个叶子节点)
- 最长路径不超过最短路径的2倍
红黑树插入流程
这是总体流程,如果可以看着这张图写出红黑树插入,后面就不用看了。
ps:强调两点
1.插入的节点一定是红颜色的,因为要保证每条路径的黑色节点个数相同,如果插入了一个黑色的节点,会扰乱所有路径的黑色节点。
2.红黑树也要像AVL一样旋转,但是它的旋转规则是看叔叔是否存在且为红颜色的。如果叔叔不存在或者叔叔为黑颜色,那么就要旋转,旋转规则和AVL一致。
3. 红黑树这里是用left,right,parent三叉链+迭代实现的
核心代码框架:
while (parent && parent->color == RED)只要parent一直为红就一直更新
{
Node* grandparent = parent->parent;
if (parent == grandparent->left)
{
Node* uncle = grandparent->right;
if (uncle && uncle->color == RED)
{
叔叔存在且为红,变色
ps:有叔叔且为红的情况变色与普通的变色操作不同
}
else
{
if (cur == parent->left)
{
//rotate right;
右旋
变色
}
else
{
//rotate left and rotate right
左右旋
变色
}
}
}
else
{
Node* uncle = grandparent->left;
if (uncle && uncle->color == RED)
{
叔叔存在且为红,变色
}
else
{
if (cur == parent->right)
{
左旋
变色
}
else
{
右左旋
变色
}
}
}
}
有叔叔的情况处理
有叔叔,只变色。
ps:有叔叔且为红的情况变色与普通的变色操作不同
变色规则如下:
把p和u变成黑色的,g变成红色的。然后让cur等于g。若此时的cur的parent和uncle还是红色,则继续重复上面的动作,直到更新到根节点,再把根节点变成黑色。
ps:叔叔可以是在parent的左边,也可以是在grandparent的右边,这点无关紧要。
伪代码:
if (uncle && uncle->color == RED)
{
parent->color = uncle->color = BLACK;
grandparent->color = RED;
cur = grandparent, parent = cur->parent;
}
这只是伪代码,后面再串起来
旋转怎么旋?
没叔叔就要旋转,具体是单旋还是双旋要具体分析。先说说旋转的过程是怎样的。
树只有一边高的情况
树一边高,证明要单旋。
右边高,则要左旋。左旋的意思就是把左子树压下来,使左右高度平衡。
左边高,则要右旋。右旋的意思就是把右子树压下来,使左右高度平衡。
总结一句就是:旋哪边就把那边的子树往下压。
左旋
注意:左旋是对grandparent左旋,也就是最高那个节点。
左旋就是右边高,要把左边压下来。如图:
对grandparent左旋的步骤如下:
- grandparent->right = parent->left
- parent->left = grandparent
成果如下:
当然,真正的代码不只这么简单。还要解决两个问题
- 由于是三叉链,因此我们也要更新parent指针的指向。
- 并且,旋转之后parent节点还要接上grandparent的parent节点。
真正的代码(对grandparent左旋)如下:
这里的parent其实就是grandparent
void RotateL(Node* parent)
{
Node* subR = parent->right;
Node* subRL = subR->left;
parent->right = subRL;
if (subRL) subRL->parent = parent;
保存parent(上图的grandparent)的parent指针,因为后面要改变了,做个备份
Node* parentparent = parent->parent;
subR->left = parent;
parent->parent = subR;
如果parent(上图的grandparent)是根了,那么就让subR(上图的parent)变成根
if (parent == root)
{
root = subR;
root->parent = nullptr;
}
否则,链接一下
else
{
//if (subR->data.second < parentparent->data.second)这样写也可以
if(parentparent->left == parent)
{
parentparent->left = subR;
subR->parent = parentparent;
}
else
{
parentparent->right = subR;
subR->parent = parentparent;
}
}
}
右旋
注意:右旋是对grandparent右旋,也就是最高那个节点。
右旋和左旋是一致的。
右旋步骤:
1.grandparent->left = parent->right;
2.parent->right = grandparent
要考虑的问题也和左旋一致,其实我觉得看了左旋之后就要会写右旋了。
右旋代码:
void RotateR(Node* parent)
{
Node* subL = parent->left;
Node* subLR = subL->right;
parent->left = subLR;
if (subLR) subLR->parent = parent;
subL->right = parent;
Node* parentparent = parent->parent;
parent->parent = subL;
if (parent == root)
{
root = subL;
root->parent = nullptr;
}
else
{
//if (subL->data.second < parentparent->data.second)这样写也可以
if(parent == parentparent->left)
{
parentparent->left = subL;
subL->parent = parentparent;
}
else
{
parentparent->right = subL;
subL->parent = parentparent;
}
}
}
树一边是左边高一边是右边高
先右边旋再左边旋
解决两个问题:
- 啥时候要先右旋再左旋?
- 左旋的是谁?右旋呢?
先对parent左旋
ps:不要按感觉旋,就按左旋的规则来,切记。
再对grandparent左旋
在红黑树这里的左右旋超简单!!!(插一句,比AVL简单多了,因为AVL要维护平衡因子)
void RotateRL(Node* parent)
{
RotateR(parent->right);
RotateL(parent);
}
先左旋再右旋
还是上面两个问题
- 啥时候要先左旋再右旋?
- 左旋的是谁?右旋呢?
当发现parent的右边高,grandparent的左边高的时候要先左旋再右旋
parent的右边高,因此先对parent左旋。具体步骤:
- cur的左子树给parent的右
- cur的左变成parent
然后对grandparent右旋:
cur的右变成grandparent的左
grandparent变成cur的右
代码:
void RotateLR(Node* parent)
{
RotateL(parent->left);
RotateR(parent);
}
没叔叔或者叔叔为黑色的情况
左边高
具体情况如下:
操作为先右旋,再变色。
左旋后的效果
由于根要是黑颜色的,因此parent要变成黑色,又由于一开始每条路径黑颜色节点只有一个,因此要把grandparent变成红色。
因此变色操作为:
parent->color = Black;
grandparent->color = Red;
整体旋转加变色的代码如下(上面已经判断过没有叔叔了,只不过代码没贴出来,后面再说):
if (cur == parent->left)
{
//rotate right;
RotateR(grandparent);
grandparent->color = RED, parent->color = BLACK;
}
右边高
右变高和左边高的操作是一样的,都是先旋转再变色。
变色操作代码也是和上面一致的。
if (cur == parent->right)
{
//rotate left;
RotateL(grandparent);
grandparent->color = RED, parent->color = BLACK;
}
左右旋
这种情况要左右旋
旋转后的效果如下:
然后变色,由于root要求是黑颜色的,因此要把cur变成black。又由于一开始所有路径的只有一个黑节点,因此又要把grandparent变成红色的。
前面已经判断了parent在grandparent左边了
if(cur == parent->right)
{
//rotate left and rotate right
RotateLR(grandparent);
grandparent->color = RED;
cur->color = BLACK;
}
右左旋转
旋转后是这个效果:
然后变色。变色逻辑和上面一样。
最后的代码:
//rotate right and rotate left
RotateRL(grandparent);
cur->color = BLACK, grandparent->color = RED;
至此,所有的情况已经分类讨论完了
最终的代码:
插入代码:
void Insert(const T& data)
{
if (root == nullptr)
{
root = new Node(data);
root->color = BLACK;
return;
}
Compare cmp;
Node* cur = root, * parent = nullptr;
while (cur)
{
if (cmp(cur->data) < cmp(data))
//if(cur->data.first < data.first)
{
parent = cur;
cur = cur->right;
}
else if (cmp(cur->data) > cmp(data))
//else if(cur->data.first > data.first)
{
parent = cur;
cur = cur->left;
}
else
{
return;
}
}
Node* newnode = new Node(data);
if (cmp(data) < cmp(parent->data)) parent->left = newnode;
//if (data.first < parent->data.first) parent->left = newnode;
else parent->right = newnode;
newnode->parent = parent;
cur = newnode;
//ReBalance
while (parent && parent->color == RED)
{
Node* grandparent = parent->parent;
if (parent == grandparent->left)
{
Node* uncle = grandparent->right;
if (uncle && uncle->color == RED)
{
parent->color = uncle->color = BLACK;
grandparent->color = RED;
cur = grandparent, parent = cur->parent;
}
else
{
if (cur == parent->left)
{
//rotate right;
RotateR(grandparent);
parent->color = BLACK;
grandparent->color = RED;
}
else
{
//rotate left and rotate right
RotateLR(grandparent);
grandparent->color = RED;
cur->color = BLACK;
}
}
}
else
{
Node* uncle = grandparent->left;
if (uncle && uncle->color == RED)
{
parent->color = uncle->color = BLACK;
grandparent->color = RED;
cur = grandparent, parent = cur->parent;
}
else
{
if (cur == parent->right)
{
//rotate left;
RotateL(grandparent);
grandparent->color = RED, parent->color = BLACK;
}
else
{
//rotate right and rotate left
RotateRL(grandparent);
cur->color = BLACK, grandparent->color = RED;
}
}
}
}
root->color = BLACK;
}
各种旋转的代码
void RotateR(Node* parent)
{
Node* subL = parent->left;
Node* subLR = subL->right;
parent->left = subLR;
if (subLR) subLR->parent = parent;
subL->right = parent;
Node* parentparent = parent->parent;
parent->parent = subL;
if (parent == root)
{
root = subL;
root->parent = nullptr;
}
else
{
//if (subL->data.second < parentparent->data.second)
if(parent == parentparent->left)
{
parentparent->left = subL;
subL->parent = parentparent;
}
else
{
parentparent->right = subL;
subL->parent = parentparent;
}
}
}
void RotateL(Node* parent)
{
Node* subR = parent->right;
Node* subRL = subR->left;
parent->right = subRL;
if (subRL) subRL->parent = parent;
Node* parentparent = parent->parent;
subR->left = parent;
parent->parent = subR;
if (parent == root)
{
root = subR;
root->parent = nullptr;
}
else
{
//if (subR->data.second < parentparent->data.second)
if(parentparent->left == parent)
{
parentparent->left = subR;
subR->parent = parentparent;
}
else
{
parentparent->right = subR;
subR->parent = parentparent;
}
}
}
void RotateLR(Node* parent)
{
RotateL(parent->left);
RotateR(parent);
}
void RotateRL(Node* parent)
{
RotateR(parent->right);
RotateL(parent);
}
验证红黑树的正确性
红黑树必须满足的两个性质:
- 没有两个连续的红节点
- 每条路径的黑色节点个数相同
从这两个地方入手。
对于没有两个连续的红节点判断思路是:正常的递归整颗树,若遇见了一个红色节点,就看它的parent节点是不是红色的,如果是红色的,就证明已经违反红黑树的性质了。
对于每条路径的黑色节点个数,先遍历一条路径,然后看其他路径的黑色节点是否和第一条路径的黑色节点的个数相同。
代码:
bool isBalance()
{
int benchmark = 0;
Node* cur = root;
while (cur)
{
benchmark++;
cur = cur->left;
}
int blacknum = 0;
return _isBalance(root, benchmark, blacknum);
}
bool _isBalance(Node* root, int benchmark, int blacknum)
{
if (root == nullptr)
{
//cout << blacknum << " " << benchmark << endl;
if (blacknum != benchmark) return false;
return true;
}
if (root->color == BLACK) blacknum++;
if (root->color == RED && root->parent->color == RED) return false;
return _isBalance(root->left, benchmark, blacknum) && _isBalance(root->right, benchmark, blacknum);
}