红黑树必须满足一下性质:
1、每个节点要么是红色要么是黑色。
2、根节点只能是黑色。
3、外部节点都是黑色。(外部节点,不是二叉树的一部分,没有实际意义,在程序中所有为空的子树都指向同一个黑色标记节点,来处理边界判断。)
4、一个为红色的节点,它的两个儿子必须是黑色。
5、每个节点,从该节点到到其子孙节点的所有路径上包含的黑节点的数目相同。
看数据结构对红黑树各种不平衡然后怎么平衡的分析,看的让我乱的很,觉得还是要步步为营,代码和理论结合,比较好掌握。
解决问题的步骤
1、左旋右旋搞定先,在平衡中肯定用的到。
2、插入节点,在插入节点后,就要维护平衡了,这是让人百转千挠的开始。
3、插入节点后根据引起不平衡的原因来恢复其平衡。插入不平衡的情况,根据左右分对称两大种,左右之中又分三种情况。算法导论上对着三种情况处理的可以说很唯美了。
在处理不平衡情况,当前不平衡状态在处理后转化为后一种情况。这三种情况可以处理任何的不平衡情况。最终使其平衡。
4、然后代码和图去结合,根据代码自己去画图,不管别人讲的再怎么浅显易懂,还是要自己去代码和图相结合那么反复的去屡屡,屡顺了,就成了你自己的东西了。一般都可以屡清楚的。
5、删除节点,在删除后,就要维护平衡,这更是让人纠结的发源地。
6、删除的不平衡,算法导论在处理上有个问题转换的步骤,这点要想通透了。他是将当前平衡节点加上了双重色,也就是当前节点的父亲“被杀了”,说的有点残忍,但他的钱财留给他的孩子,这里也就是他的颜色。这种转换其实是“无中生有”的一种假设。那么现在树的情况是这样的,当前节点具有两种颜色。也就是违反了上面提到的性质1。而红黑树的其他条件都满足。
如果父亲留下的是红色,那么整个树还是平衡的。不用再纠结了。而如果父亲留下的是黑色,当前节点是红色,那么直接把黑色继承过来。整个树还是“和谐的”。而如果父亲留下的是黑色的而当前节点也是黑色的,那就打破了树的“和谐”规则,要把这个黑色向上移动,直到黑色交给一个红色或是根节点了,或是做合适的旋转和颜色更改,来释放具有双黑色的节点。
#pragma once
enum Color{Red,Black};
struct cNode
{
Color color;
int mvalue;
cNode *parent,
*left,
*right;
};
class RBTree
{
public:
RBTree(void);
~RBTree(void);
//插入新的节点
RBTree& RBInsert(int key)
{
Insert(key,pFlag);
return *this;
}
//删除节点
RBTree& RBDelete(int key)
{
Delete(key,pFlag);
return *this;
}
private:
//插入节点的实际操作
void Insert(int key,cNode* t=NULL);
//删除节点的实际操作
void Delete(int key,cNode* t=NULL);
//返回真正删除的节点指针
cNode* TreeSuccessor(cNode* root,int key,cNode* t=NULL);
//左旋
void LeftRotate(cNode* c,cNode* t=NULL);
//右旋
void RightRotate(cNode* c,cNode* t=NULL);
//插入平衡维护
void InsertFixUp(cNode* c,cNode* t=NULL);
//删除平衡维护
void DeleteFixUp(cNode* c,cNode* t=NULL);
private:
cNode* pRoot;//根节点
cNode* pFlag;//额外标记节点,处理边界问题
};
要平衡左旋和右旋是少不了的,左旋右旋实现相对简单
左旋代码:
void RBTree::LeftRotate(cNode* c,cNode* t/* =NULL */)
{
cNode* y=c->right;
c->right=y->left;
if(y->left!=t)
y->left->parent=c;
y->parent=c->parent;
if(c->parent==t)
pRoot=y;
else if(c==c->parent->left)
{
c->parent->left=y;
}else
c->parent->right=y;
y->left=c;
c->parent=y;
}
右旋代码:
void RBTree::RightRotate(cNode* c,cNode* t/* =NULL */)
{
cNode* y=c->left;
c->left=y->right;
if(y->right!=t)
{
y->right->parent=c;
}
y->parent=c->parent;
if(c->parent==t)
pRoot=y;
else if(c==c->parent->left)
c->parent->left=y;
else
c->parent->right=y;
y->right=c;
c->parent=y;
}
插入新节点代码:
插入的新节点其颜色设置为红色。因为如果设置为黑色则必定破坏了平衡性。而设置为红色则是可能破坏了。破坏的情况为,新节点的父节点颜色也为红色。这时就需要做平衡性的维护了。
void RBTree::Insert(int key,cNode* t/* =NULL */)
{
cNode* y=t;
cNode* x=pRoot;
while(x!=t)
{
y=x;
if(x->mvalue>key)
x=x->left;
else if(x->mvalue==key)
return ;
else
x=x->right;
}
cNode* z=new cNode();
z->parent=y;
if(y==t)
{
pRoot=z;
}
else if(key<y->mvalue)
y->left=z;
else
y->right=z;
z->left=t;
z->right=t;
z->color=Red;
//维持平衡
InsertFixUp(z,t);
}
插入新节点维护树平衡性的代码
void RBTree::InsertFixUp(cNode* c,cNode* t/* =NULL */)
{
while(c->parent->color==Red)
{
cNode* gc=c->parent->parent;
cNode* pc=c->parent;
cNode* y=t;
if(pc==gc->left)
{
y=gc->right;
if(y->color==Red)
{
pc->color=Black;//情况1
y->color=Black; //情况1
gc->color=Red;//情况1
c=gc; //情况1
}
else
{
if(c==pc->right)
{
c=pc; //情况2
LeftRotate(c,t);//情况2
}
c->parent->color=Black;//情况3
c->parent->parent->color=Red;//情况3
RightRotate(c->parent->parent,t);//情况3
}
}
else
{
y=gc->left;
if(y->color==Red)
{
pc->color=Black;
y->color=Black;
gc->color=Red;
c=gc;
}
else
{
if(c==pc->left)
{
c=pc;
RightRotate(c,t);
}
c->parent->color=Black;
c->parent->parent->color=Red;
LeftRotate(c->parent->parent,t);
}
}
}
pRoot->color=Black;
}
情况1:如图1所示,经过情况1的处理后如图2
图1 图2
这时候程序循环重头进行,现在节点7是C,节点14是y如图3。再继续处理变成如图4的情况,这时变成情况3,再接着处理。变成图5.平衡了现在。
图3 图4
图5
删除节点代码:
在删除节点时使用了一个TreeSuccessor的函数来获取删除节点。
void RBTree::Delete(int key,cNode* t/* =NULL */)
{
cNode* y=TreeSuccessor(pRoot,key,t);
cNode* x=t;
if(y->left!=t)
{
x=y->left;
}
else
x=y->right;
x->parent=y->parent;
if(y->parent==t)
pRoot=x;
else if(y==y->parent->left)
y->parent->left=x;
else
y->parent->right=x;
if(y->color==Black)
DeleteFixUp(x,t);
delete y;
return ;
}
这个函数的作用就是是,要删除的节点变成为要么只有左子树要么只有右子树,要么没有子树。
cNode* RBTree::TreeSuccessor(cNode* root,int key,cNode* t)
{
cNode* p=root;
cNode* pp=0;
while(p!=t)
{
pp=p;
if(p->mvalue>key)
p=p->left;
else if(p->mvalue==key)
break;
else
p=p->right;
}
if(p->right!=t&&p->left!=t)
{
p->mvalue=p->left->mvalue;
key=p->mvalue;
TreeSuccessor(p->left,key,t);
}
return p;
}
删除一个节点后,平衡性维护的代码:
void RBTree::DeleteFixUp(cNode* c,cNode* t/* =NULL */)
{
while(c!=pRoot&&c->color==Black)
{
if(c==c->parent->left)
{
cNode* pc=c->parent;
cNode* w=pc->right;
if(w->color==Red)
{
w->color=Black; //情况1
pc->color=Red; //情况1
LeftRotate(pc,t); //情况1
w=pc->right; //情况1
}
if(w->right->color==Black&&w->left->color==Black)
{
w->color=Red; //情况2
c=c->parent; //情况2
}
else if(w->right->color==Black)
{
w->color=Red; //情况3
w->left->color=Black; //情况3
RightRotate(w,t); //情况3
w=c->parent->right; //情况3
w->color=c->parent->color; //情况4
c->parent->color=Black; //情况4
w->right->color=Black; //情况4
LeftRotate(c->parent,t); //情况4
c=pRoot; //情况4
}
}
else
{
cNode* pc=c->parent;
cNode* w=pc->left;
if(w->color==Red)
{
w->color=Black;
pc->color=Red;
RightRotate(pc,t);
w=pc->left;
}
if(w->right->color==Black&&w->left->color==Black)
{
w->color=Red;
c=c->parent;
}
else if(w->left->color==Black)
{
w->color=Red;
w->right->color=Black;
LeftRotate(w,t);
w=c->parent->left;
w->color=c->parent->color;
c->parent->color=Black;
RightRotate(c->parent,t);
c=pRoot;
}
}
}
c->color=Black;
}
在解决删除问题上使用了“无中生有”双重色方法,但它违反了节点只能是单色的原则。要解决这个问题就要把这个黑色向上移动,直到黑色交给一个红色或是根节点了,或是做合适的旋转和颜色更改,来释放具有双黑色的节点。
代码中处理情况1,使其成为情况2,或是情况3、4。进行处理。而情况2的处理就是把双重色向上移,情况3,4的处理就是旋转和变色来消解双重色。
情况1的处理:
情况1变换前 情况1变换后
这时候如果w两个左右子树的根节点都是黑色,则进行情况2处理:变换后c为灰色表示c为双色节点。这是程序循环又从头开始,处理。
情况2变换前 情况2变换后
如果经过情况1处理后新的W节点只有其右子树的根为黑色,则进行情况3的变化。
情况3变换前 情况3变换后
经过情况3的处理后,就可以直接情况4的处理,来稀释双重色节点了。这里的灰色表示是红色或者是黑色都可以没关系了。再经过情况4处理后相当于双重色节点c上又补了一个黑色的节点。代替了被删除的黑色节点,这时候树整个都平衡了。这里会有疑问那原来的6节点是什么颜色,怎么办。变化后由原来的节点7补上,而把6节点原来的颜色(不管他是什么颜色赋给7节点,7再旋转代替6的位置)赋给7节点。这时候c就不用再装成双重色节点了。而是真的多出了一个黑节点。“无中生有”完成。
情况4变换前 情况4变换后
代码可能有bug。发现了还望指正,谢谢。