删除须知
红黑树的性质
- 性质1:节点都是红色活着黑色
- 性质2:根节点是黑色
- 性质3:所有叶子节点都是黑色(叶子是Nil节点)
- 性质4:每个红色节点的还自己节点只能是黑色
- 性质5:对每个节点,从该节点到其所有后代叶结点的简单路径上,均包含相同数目的黑色节点
标题
由于最近在实现红黑树删除时遇到诸多麻烦,查看了许多篇文章,但终未为我问题的解决提供帮助,因此作此篇文章供以帮助,若有不妥之处 望各位大佬不吝指正。
红黑树节点结构
template <class Type>
class RBNode
{
friend class RBTree<Type>;
public:
RBNode(Type d=Type(),RBNode<Type> *left=nullptr,RBNode<Type> *right=nullptr)
:data(d),leftChild(left),rightChild(right),parent(nullptr),color(RED)
{}
private:
Type data;
RBNode<Type>* leftChild;
RBNode<Type>* rightChild;
RBNode<Type>* parent;
Color_t color;
};
节点名称规定
被删节点为x
x的父亲节点为father
x的兄弟节点为bother
x的侄子节点为son
临时指针temp
单旋转
删除函数
RBNode<Type>* Erase(RBNode<Type>* x)
delete的是函数Erase的返回值
红黑树删除
删除情况总列
- 情况一:x为红,且无子女节点
- 情况二:x为红,且仅有一个子女节点(不可能发生)
- 情况三:x有两个子女节点(x为红–两个子女节点 AND x为黑–两个子女节点)
- 情况四:x为黑,且仅有一个子女节点
- 情况五:x为黑,且无子女节点
情况一:x为红,且无子女节点
----------father的左孩子置空---------------------------------------- father的右孩子置空---------
返回x进行delete
if (x->parent->leftChild == x)
x->parent->leftChild = Nil;
else
x->parent->rightChild = Nil;
x->color = BLACK;//为情况五服务不影响情况一的删除
情况二:x为红,且仅有一个子女节点
x为红,且有一个孩子节点,则孩子节点必为黑,即如图:x的左子树路径上有一个黑色,而x的右子树上有两个黑色,不满足性质五,因此这种情况不可能存在,不讨论
情况三:x有两个子女节点
对有两个孩子节点的节点删除,实则是把x->data的值替换为与x->data相邻大小的值。即替换为左子树最大值或者右子树最小值,再对替换的节点进行删除
其中A为x的左子树的最大值,B为x的右子树的最小值
将x->data替换为A->data或者B->data,再将A节点或者B节点删除,如此便已转化为删除黑色叶子节点
此时转为情况五将41节点进行删除,最后将41的值赋值给原来的x->data。注意返回时需返回A/B(41)节点的地址。
RBNode<Type>* temp;
temp=Max_t(x->leftChild);
Erase(temp);
x->data = temp->data;
return temp;
情况四:x为黑,且有一个子女节点(仅有的子女节点必为红色)
此时x的子女节点必为红,原因如下
如图若x为黑且只有一个孩子节点为黑,此时便不满足红黑树的性质五
因此x的孩子节点的颜色必为红色
将x的父节点与x的孩子节点相连,返回x节点进行delete
if (x->leftChild->color == RED)
{//孩子节点在左侧,右侧为Nil
if (x->parent->leftChild == x)
x->parent->leftChild = x->leftChild;
else
x->parent->rightChild = x->leftChild;
x->leftChild->parent = x->parent;
x->leftChild->color = BLACK;
}
else
{//孩子节点在右侧,左侧为Nil
if (x->parent->leftChild == x)
x->parent->leftChild = x->rightChild;
else
x->parent->rightChild = x->rightChild;
x->rightChild->parent = x->parent;
x->rightChild->color = BLACK;
}
情况五:x为黑,且无子女节点
这个情况较为复杂,分为五种情形 如下:
情形一:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为外侧
情形二:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为内侧
情形三:被删节点的兄弟节点为黑色,且其兄弟节点无孩子节点
情形四:被删节点的兄弟节点为红色,即它必有黑色的孩子节点
情形五:被删节点的兄弟节点为黑色,其兄弟节点的孩子节点都为黑色,且不为Nil
情形一:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为外侧
情形特点【x为黑色,无孩子节点,bother为黑色,至少有一个红色节点,且该红色节点为外侧】
通过旋转使得father替换x的位置,bother替换father的位置,son替换bother的位置,如此就相当于将即将删除的x节点的黑色转移到son中,使得该树在删除x节点后满足性质五。
旋转过后对各个节点的颜色进行赋值,需要注意的是father的颜色可红可黑,要将father的颜色赋给bother,最后对其他节点颜色进行赋值,再返回x节点进行delete
if (bother->color == BLACK && bother->rightChild->color == RED)
//情形一:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为外侧
{
//单旋转
Color_t c = father->color;
son = bother->rightChild;
Single_rotation(son, bother, father, x);
bother->color = c;
father->color = BLACK;
son->color = BLACK;
father->leftChild = Nil;
}
情形二:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为内侧
情形特点【x为黑色,无孩子节点,bother为黑色,至少有一个红色节点,且该红色节点为内侧】
此情形与情形一相似,只需将son位置和bother的位置进行调整便变为情形一,按照情形一对其操作,便保证了红黑树的性质,最后返回x节点进行delete
else if (bother->color == BLACK && bother->leftChild->color == RED)
{//情形二:被删节点的兄弟节点为黑色,且其兄弟节点有孩子节点为红色,孩子为内侧
Color_t c = father->color;
son = bother->leftChild;
bother->leftChild = son->rightChild;
if (son->rightChild != Nil)
son->rightChild->parent = bother;
father->rightChild = son->rightChild;
if (son->leftChild != Nil)
son->leftChild->parent = father;
father->rightChild = son;
son->parent = father;
son->rightChild = bother;
bother->parent = son;//以上将其调整为外侧
Single_rotation(bother, son, father, x);//单旋转
son->color = c;
father->color = BLACK;
bother->color = BLACK;
father->leftChild = Nil;
//father->rightChild = Nil;
}
情形三:被删节点的兄弟节点为黑色,且其兄弟节点无孩子节点
情形特点【x为黑色,无孩子节点,bother为黑色,无孩子节点】
此情形中又分为两种情况father为红,father为黑,如上图
b.1 father为红
思路上,是要将x节点的黑色转移到father中,再将bother的黑色转为红色,如此便满足红黑树各条性质
b.2 father为黑
当father时,将x的黑色转移到father处,用Erase(father)函数调整father内多余的黑色,但为了防止递归死循环,调用前需先将father的孩子节点断开赋空。
最后将bother赋红,连接father与bother即可。
返回x进行delete
else if (bother->color == BLACK && bother->leftChild == Nil && bother->rightChild == Nil)
{//情形三:兄弟节点为黑色,且无孩子节点
if (father->color == RED)
{//父节点为红色
bother->color = RED;
father->color = BLACK;
}
else
{//父节点为黑色
father->leftChild = Nil;
father->rightChild = Nil;
//断开孩子节点
Erase(father);
if (bother->data < father->data)
father->leftChild = bother;
else
father->rightChild = bother;
//连接father与bother节点
if (father->data < father->parent->data)
father->parent->leftChild = father;
else
father->parent->rightChild = father;
//连接father与father->parent
bother->color = RED;
}
x->parent->leftChild = Nil;
//father->rightChild = Nil;
}
情形四:被删节点的兄弟节点为红色,即它的孩子节点和父节点必为黑
对bother的外侧孩子进行单旋转,将bother赋黑,father赋红,此时x产生了新的bother再调用Erase(x)函数(情况五的情形三b.1)返回x进行delete
else if(bother->color == RED)
{
//情形四:被删节点的兄弟节点为红色,即它的孩子节点和父节点必为黑
father->rightChild = bother->leftChild;
bother->leftChild->parent = father;
if (father->parent->leftChild == father)
father->parent->leftChild = bother;
else
father->parent->rightChild = bother;
bother->parent = father->parent;
bother->leftChild = father;
father->parent = bother;
//单旋转
father->color = RED;
bother->color = BLACK;
Erase(x);
}
情形五:被删节点的兄弟节点为黑色,其兄弟节点的孩子节点都为黑色,且不为Nil
正常情况不可能直接进入此情形(原因是直接进入时 x没有孩子节点,而bother为黑且还有两个黑色的孩子节点,便不满足性质五,所以不可能直接进入),进入此情形是必由情况三的情形三的b.2调用函数进入的。则此时x存储了两个黑色,且兄弟节点及其孩子节点为黑色:
第一步:我们需要断开father与其孩子节点的连接(视为x处的黑色转移到了father处)接下来需要将father多余的黑色转移
第二步:调用Erase(father)调整father的黑色【1、把红色的father覆盖(情况一);2、转移到father的红色兄弟节点上(情况四);3、转移到father的红色子侄节点(情况五的情形一/二);4、father节点,father的兄弟节点,father的子侄节点均为黑(情况五的情形五)】下方图c系列 详解
返回后father的黑色已被转移
第三步:将father与father->parent重新进行连接,原因是在传入参数father的Erase函数内视为要将father删除,会断开father与father->parent的连接。
第四步:重新连接father与孩子节点的联系,因为在调用Erase前断开了father与其孩子节点的联系,此时也需重新进行连接(注:左右孩子都需要连接,该逻辑内,x不是真正要被删除的节点)
最后返回至情况五的情形三的b.2
else if (bother->leftChild != Nil && bother->rightChild != Nil && bother->leftChild->color == BLACK && bother->rightChild->color == BLACK)
{//情形五:被删节点的兄弟节点为黑色,其兄弟节点的孩子节点都为黑色,且不为Nil
bother->color = RED;
father->leftChild = Nil;
father->rightChild = Nil;
Erase(father);
if (father->data < father->parent->data)
father->parent->leftChild = father;
else
father->parent->rightChild = father;
//连接father和father->parent
if (father->data > bother->data)
{
father->leftChild = bother;
father->rightChild = x;
}
else
{
father->leftChild = x;
father->rightChild = bother;
}
//连接father和两个孩子节点
}
调整father的黑色:三种节点:x的父节点,x的兄弟节点,x的子侄节点
图c.1便是删除x节点时,三种节点都为黑色,向上递归调用被视为情况五中的情形五处理,通过情形五处理后,出现的是红色的父节点。
图c.2是最后一次调用Erase(father),函数中将被视为情况一进行处理,需要注意的是此处需要为这个红色父节点赋黑,由于情况一认为删除的就是此红节点,所以在情况一的逻辑内中对x的颜色进行赋黑处理,是不会影响情况一的删除逻辑的。
图c.3便是删除x节点时,三种节点都为黑色,向上递归调用被视为情况五中的情形五处理,通过情形五处理后,出现的是红色的兄弟节点。
图c.4是最后一次调用Erase(father),函数中将被视为情况五中的情形四处理
图c.5便是删除x节点时,三种节点都为黑色,向上递归调用被视为情况五中的情形五处理,通过情形五处理后,出现的是红色的子侄节点。
图c.4是最后一次调用Erase(father),函数中将被视为情况五中的情形四处理
删除也就到此完毕,欢迎留言评论
红黑树删除源码
测试函数
#include"Insert.h"
#include"Eease.h"
#include<map>
#include<vector>
using namespace std;
/* 检查黑色平衡、指针、结点颜色,函数返回黑色高度 */
template<class Type>
int RBTree<Type>::check_tree(RBNode<Type>* temp)
{
if (temp == Nil)
return 0;
/* 黑色节点计数,判断黑色平衡 */
int ct_black = 0;
if (temp->color == BLACK)
ct_black = 1;
/* 判断parent指针是否正确 */
if (temp->leftChild != Nil && temp->leftChild && temp->leftChild->parent != temp)
{
//Print_Tree();
cout << temp->data << "ERROR: pointer left error: " << temp->leftChild->data << endl;
exit(-1);
}
if (temp->rightChild != Nil && temp->rightChild && temp->rightChild->parent != temp)
{
cout << temp->data << "ERROR: pointer right error: " << temp->rightChild->data << endl;
exit(-1);
}
/* 判断是否有两个连续红色节点 */
if (temp->color == RED)
{
// tourist();
if (temp->leftChild != nullptr && temp->leftChild->color == RED)
{
cout << temp->data << "ERROR: color error with left: " << temp->leftChild->data << endl;
exit(-1);
}
if (temp->rightChild != nullptr && temp->rightChild->color == RED)
{
cout << temp->data << "ERROR: color error with right: " << temp->rightChild->data << endl;
exit(-1);
}
}
/* 判断黑色平衡 */
int left_black = check_tree(temp->leftChild);
int rrr = check_tree(temp->rightChild);
if (left_black != rrr)
{
cout << temp->data << endl;
cout << "ERROR: check_Node error in color" << endl;
exit(-1);
}
return ct_black + left_black;
}
/* BST中序遍历的得到的key从小到大 */
template<class Type>
void RBTree<Type>::isValidBST(RBNode<Type>* root) {
stack<RBNode<Type>* > st;
RBNode<Type>* last = Nil, * now = root;
while (true)
{
while (now != Nil)
{
st.push(now);
now = now->leftChild;
}
if (st.size())
now = st.top();
else return;
st.pop();
if (last != Nil && now->data <= last->data)
{
std::cout << "EROOR: not BST" << std::endl;
exit(-1);
}
last = now;
now = now->rightChild;
}
return;
}
/* 判断红黑树的正确性 */
template<class Type>
void RBTree<Type>::isVaildRbTree()
{
/* 判断是否是BST */
check_tree(root);
/* 判断红黑树其他要求 */
isValidBST(root);
if (root != Nil && root->color == RED)
{
std::cout << "ERROR: root color is not black" << std::endl;
exit(-1);
}
}
/* 测试函数 */
void test(int n)
{
RBTree<int> rbTree{};
map<int, int> mp; /* 标记已经生成的key */
vector<int> index; /* 保存key方便删除 */
/* 测试插入 */
while (n--)
{
/* 随机生成key */
int key;
while (mp.find(key = rand() % 1000) != mp.end())
{
}
mp[key] = 1;
/* 保存key */
index.push_back(key);
cout << "insert key: " << key << endl;
rbTree.Insert(key);
rbTree.check_tree(rbTree.get_root());
// rbTree.tourist();
}
rbTree.Print_Tree();
cout << "--------------------------------" << endl;
cout << "--------------------------------" << endl;
/* 随机将插入的key删除 */
while (index.size())
{
/* 随机生成已有的key并删除对应结点 */
int id = rand() % index.size();
int key = index[id];
cout << "erase key: " << key << endl;
index.erase(index.begin() + id);
rbTree.Erase(key);
rbTree.check_tree(rbTree.get_root());
}
cout << "rb_tree test success" << endl;
}
int main()
{
test(500);
return 0;
}