红黑树
一.简介
1.1定义
红黑树(red black tree)是BST的一种,AVL的变种。其操作在最坏的情况下为O(logN),而且其插入操作可以相对容易的非递归的实现。红黑树定义如下:
- 红黑树是节点为红/黑色的BST:左小右大、中根遍历有序;
- root是黑色的;
- 红节点的子节点必须是黑色的,反之不必成立;
- 每个节点到null指针的路径必须包含相同颜色数目的黑节点;
由着色法则可推出红黑树的最大高度是2log(N+1);
[补充]
- BST:每个节点左子树所有节点值小于他,右子树所有节点相反;
- AVT:每个节点高度相差不超过1;
二.基本操作
同其他树一样,红黑树的基本操作也是插入、查找、删除。红黑树难的操作是插入一个节点。
向红黑树插入节点时,如果放在叶子节点处:
- 如果父节点是黑色的,则插入节点为红色,success;
- 如果父亲节点是红色的,如果插入节点如果是黑色则会违反规则4——产生节点到null指针路径上的黑色节点不相同,如果插入节点红色,则违反规则3——红色节点子节点必是黑色,即红红不相连。此时可以通过改变树节点的颜色和旋转树完成插入操作
2.1自底向上的插入
插入节点父节点的兄弟节点为黑色(null也是黑色)的前提下:插入节点初始颜色为红色,然后通过“旋转+变色”的操作来保持红黑树的特性,变色指进行旋转的节点变成另一种颜色。插入节点如果父节点为红色一般分为两种情况:
- 插入节点X、父亲节点P和祖父节点G是一字型,此时P+G旋转并变色即可;
- X、P和G如果是之字形,则X+P先旋转变色,然后X+G旋转变色即可。示意图如下:
2.2自顶向下的红黑树_插入
2.1的前提是保证付P的兄弟节点S是黑色——对红黑树应用自顶向下就是保证S是黑色的过程。详情如下:
自顶向下寻找X插入的位置,这个过程中:
- 如果当前节点两个Q的两个孩子节点都是红色,则Q变为红色,两个孩子变为黑色;
- 在执行1后,如果Q的父节点Qp是红色,则执行2.1所说的(单/双)旋转——Qp的另一个孩子不可能与Qp同为红色;
通过以上逻辑找到插入位置时,X的父亲节点和叔叔节点不可能同为红色的了。示例图如下(双圈代表红色),自顶向下插入45:
2.3一次自顶向下的基本操作:递归遍历、初始化、单旋转、插入
1)插入
遍历代码如下:
//中根遍历:打印元素递增
void print(RedBlackTree T){
DoPrint(T->Right);
}
static void DoPrint(RedBlackTree T){
if(T!=NullNode){
DoPrint(T->Left);
Output(T->Element);
DoPrint(T->Right);
}
}
2)节点的声明和树的初始化
有两个标记节点:一个是跟标记,存放-∞
,其右子节点指向真正的根,一个是NullNode
,指代叶子节点的下一个节点,也就是Null
//颜色类型
typedef enum ColorType{Red,Black} ColorType;
//应该还有两行书上没写的,以下
//typedef struct RedBlackNode *RedBlackTree;
//typedef struct RedBlackNode *Position;
//红黑树节点
struct RedBlackNode{
ElementType Element;
RedBlackTree Left;
RedBlackTree Right;
ColorType Color;
}
Position NullNode=NULL;
//初始化
RedBlackTree Initialize(void ){
RedBlackTree T;
//如果NullNode为空:黑色、负无穷大、左右指向自身
if(NullNode==NULL){
NullNode==malloc(sizeof(struct RedBlackNode));
NullNode->Left=NullNode->Right=NullNode;//NullNode左右节点都指向自身;
NullNode->Color=Black;//NullNode颜色为黑色;
NullNode->Element=Infinity;//也就是∞
}
T=malloc(sizeof(struct RedBlackNode));
T->Element=NegInfinity;//也就是-∞
T->Left=T->Right=NullNode;
T->Color=Black;
return T;
}
3)节点的旋转
因为得到的数必须连接到父节点上,所以Rotate吧父节点作为一个参数;
static Postion Rotate(ElementType Item,Position Parent){
if(Item < Parent->Element){
return Parent->Left=Item < Parent->Left->Element?singleRotateWithLeft(Parent->Left):SingleRotateWithRight(Parent->Left);
}
else
return Parant->Right=Item < Parent->Right->Element?SingleRotateWithLeft(Parent->Right):SingleRotateWithRight(Parent->Right);
}
至于SingleRotateWithLeft
函数,猜想其代码如下(参考P89):
static Position SingleRotateWithLeft(Position K2){
//旋转
Posotion K1=K2.left;
K2->Left=K1.Right;
K1.Right=K2;
//TODO K1、K2变成另一种颜色
return K1;
}
3)插入过程
static Position X,P,GP,GGP;
//翻转颜色
static void HandleReorient(ElementType Item,RedBlackTree T){
//颜色反转
X->Color=Red;
X->Left->Color=Black;
X->Right->Color=Black;
if(P->Color=Red){//如果X的父亲也是红色的,此时应该翻转
GP->Color=Red;
//如果Item不同时小于P和GP,即父节点和祖父节点
if((Item < GP->Element)!=(Item<P->Element))//start double ratation
P=Rotate(Item,GP);
X=Rotate(Item,GGP);
X->Color=Black;
}
T->Right->Color=Black;//使根节点为黑色
}
//插入节点
RedBlackTree Insert(ElementType Item,RedBlackTree T){
X=P=GP=T;
NullNode->Element=Item;
while(X->Element!=Item){//开始下滤
GGP=Gp;
Gp=P;
P=X;
if(Item < X->Element)
X=X->Left;
else
X=X->Right;
if(X->Left->Color==Red&&X->Right->Color==Red)
HandleReorient(Item,T);
}//end fo 下滤
if(X!=NullNode)
return NullNode;
X=malloc(sizeof(struct RedBlackNode));
X->Element=Item;
X->Left=X->Right=NullNode;
if(Item<P->Elemnet)
P->Left=X;
else
P->Right=X;
HandleReorient(Item,T);
return T;
}
2.4删除节点
BST deletion
首先回顾如果我们是在BST——T上删除目标值t的操作步骤:如果$n_t$
(存放目标值t的节点)至多只有一个孩子,则用其孩子或者"terminal_node"代替;如果$n_t$
有两个孩子,则用$n_t$
的后继节点——即右子树最小的节点s(t)
代替——而s(t)
则用上边的方法删除——用其唯一子节点或者"terminal_node"代替。
在回顾红黑树的原则:
- 每个节点到其可达叶子节点经过的黑节点是一样的;
而我们用BST的方式删除节点时,至少会缩短一个节点到叶子节点的路径长度。如果所删除的接单是红色的,则不会破坏以上红黑树原则,黑色则会破坏其原则。
The Approach
我们删除节点的方式是:如果被删除的节点时红色则直接用BST的方式删除就好。如果是红色的,则: