B-树、B+树、红黑树
来源:互联网 发布:球球大作战隐身软件 编辑:程序博客网 时间:2024/06/13 00:34
B-树
B-tree树即B树,B即Balanced,平衡的意思,B-树又称为多路平衡查找树。因为B树的原英文名称为B-tree,而国内很多人喜欢把B-tree译作B-树,其实,这是个非常不好的直译,很容易让人产生误解。如人们可能会以为B-树是一种树,而B树又是另一种树。而事实上是,B-tree就是指的B树。
一、定义
B-树是一种多路搜索树(并不一定是二叉的)
二、概念
- 定义任意非叶子结点最多只有M个儿子;且M>2;
三、特性
(1)根结点只有1个,关键字字数的范围[1,m-1],分支数量范围[2,m];
(2)除根以外的非叶结点,每个结点包含分支数范围[[m/2],m],即关键字字数的范围是[[m/2]-1,m-1],其中[m/2]表示取大于m/2的最小整数;
(3)非叶结点是由叶结点分裂而来的,所以叶结点关键字个数也满足[[m/2]-1,m-1];
四、B树查找的算法思想
1、B-树的查找
B-树的查找过程:根据给定值查找结点和在结点的关键字中进行查找交叉进行。首先从根结点开始重复如下过程:
若比结点的第一个关键字小,则查找在该结点第一个指针指向的结点进行;若等于结点中某个关键字,则查找成功;若在两个关键字之间,则查找在它们之间的指针指向的结点进行;若比该结点所有关键字大,则查找在该结点最后一个指针指向的结点进行;若查找已经到达某个叶结点,则说明给定值对应的数据记录不存在,查找失败。
2. B-树的插入
插入的过程分两步完成:
(1)利用前述的B-树的查找算法查找关键字的插入位置。若找到,则说明该关键字已经存在,直接返回。否则查找操作必失败于某个最低层的非终端结点上。
(2)判断该结点是否还有空位置。即判断该结点的关键字总数是否满足n<=m-1。若满足,则说明该结点还有空位置,直接把关键字k插入到该结点的合适位置上。若不满足,说明该结点己没有空位置,需要把结点分裂成两个。
分裂的方法是:生成一新结点。把原结点上的关键字和k按升序排序后,从中间位置把关键字(不包括中间位置的关键字)分成两部分。左部分所含关键字放在旧结点中,右部分所含关键字放在新结点中,中间位置的关键字连同新结点的存储位置插入到父结点中。如果父结点的关键字个数也超过(m-1),则要再分裂,再往上插。直至这个过程传到根结点为止。
3、B-树的删除
在B-树上删除关键字k的过程分两步完成:
(1)利用前述的B-树的查找算法找出该关键字所在的结点。然后根据 k所在结点是否为叶子结点有不同的处理方法。
(2)若该结点为非叶结点,且被删关键字为该结点中第i个关键字key[i],则可从指针son[i]所指的子树中找出最小关键字Y,代替key[i]的位置,然后在叶结点中删去Y。
因此,把在非叶结点删除关键字k的问题就变成了删除叶子结点中的关键字的问题了。
在B-树叶结点上删除一个关键字的方法是
首先将要删除的关键字k直接从该叶子结点中删除。然后根据不同情况分别作相应的处理,共有三种可能情况:
(1)如果被删关键字所在结点的原关键字个数n>=ceil(m/2),说明删去该关键字后该结点仍满足B-树的定义。这种情况最为简单,只需从该结点中直接删去关键字即可。
(2)如果被删关键字所在结点的关键字个数n等于ceil(m/2)-1,说明删去该关键字后该结点将不满足B-树的定义,需要调整。
调整过程为:如果其左右兄弟结点中有“多余”的关键字,即与该结点相邻的右(左)兄弟结点中的关键字数目大于ceil(m/2)-1。则可将右(左)兄弟结点中最小(大)关键字上移至双亲结点。而将双亲结点中小(大)于该上移关键字的关键字下移至被删关键字所在结点中。
(3)如果左右兄弟结点中没有“多余”的关键字,即与该结点相邻的右(左)兄弟结点中的关键字数目均等于ceil(m/2)-1。这种情况比较复杂。需把要删除关键字的结点与其左(或右)兄弟结点以及双亲结点中分割二者的关键字合并成一个结点,即在删除关键字后,该结点中剩余的关键字加指针,加上双亲结点中的关键字Ki一起,合并到Ai(是双亲结点指向该删除关键字结点的左(右)兄弟结点的指针)所指的兄弟结点中去。如果因此使双亲结点中关键字个数小于ceil(m/2)-1,则对此双亲结点做同样处理。以致于可能直到对根结点做这样的处理而使整个树减少一层。
总之,设所删关键字为非终端结点中的Ki,则可以指针Ai所指子树中的最小关键字Y代替Ki,然后在相应结点中删除Y。对任意关键字的删除都可以转化为对最下层关键字的删除。
如图示:
a、被删关键字Ki所在结点的关键字数目不小于ceil(m/2),则只需从结点中删除Ki和相应指针Ai,树的其它部分不变。
b、被删关键字Ki所在结点的关键字数目等于ceil(m/2)-1,则需调整。调整过程如上面所述。
c、被删关键字Ki所在结点和其相邻兄弟结点中的的关键字数目均等于ceil(m/2)-1,假设该结点有右兄弟,且其右兄弟结点地址由其双亲结点指针Ai所指。则在删除关键字之后,它所在结点的剩余关键字和指针,加上双亲结点中的关键字Ki一起,合并到Ai所指兄弟结点中(若无右兄弟,则合并到左兄弟结点中)。如果因此使双亲结点中的关键字数目少于ceil(m/2)-1,则依次类推。
五、B-树的C语言描述
1、存储结构
2、插入
3、查找
六、B-树的C语言实现
#include "stdio.h"#include "stdlib.h"
#include "math.h"
#define OK 1
#define ERROR -1
#define m 3 //3阶树
#define N 16 //数据元素个数
#define MAX 5 //字符串最大长度+1
typedef int KeyType;
struct Others //记录的其它部分
{
char info[MAX];
};
struct Record
{
KeyType key; //关键字
Others others; //其它部分
};
typedef struct BTNode
{
int keynum; //结点中关键字个数
BTNode *parent;//指向双亲节点
struct Node //结点向量类型
{
KeyType key; //关键字向量
BTNode *ptr;//子树指针向量
Record *recptr; //记录向量指针
}node[m+1]; //key,recptr的0号单元未用
}BTNode,*BTree;
struct Result //B树的查找结果类型
{
BTNode *pt; //指向找到的结点
int i; //在节点中关键字序号,1...m
int tag; //1表示查找成功,0表示查找失败。
};
int InitDSTable(BTree &DT)
{
DT=NULL;
return OK;
}//InitDSTable
void DestroyDSTable(BTree &DT)
{
int i;
if(DT) //非空树
{
for(i=0;i<=DT->keynum;i++)
DestroyDSTable(DT->node[i].ptr);
free(DT);
DT=NULL;
}//if
}//DestroyDSTable
int Search(BTree p,KeyType K)
{//在p->node[1...keytype].key中查找i,使得p->node[i].key<=K<
//p->node[i+1].key
int i=0,j;
for(j=1;j<=p->keynum;j++)
if(p->node[j].key<=K)
i=j;
return i;
}//Search
void Insert(BTree &q,int i,Record *r,BTree ap)
{//将r->key、r和ap分别插入到q->key[i+1]、
//q->recptr[ i+1]和q->ptr[i+1]中
int j;
for(j=q->keynum;j>i;j--) //空出q->node[i+1]
q->node[j+1]=q->node[j];
q->node[i+1].key=r->key;
q->node[i+1].ptr=ap; //前加入的结点,还没有儿子结点
q->node[i+1].recptr=r;
q->keynum++;
}//Insert
void NewRoot(BTree &T,Record *r,BTree ap)
{// 生成含信息(T,r,ap)的新的根结点*T,原T和ap为子树指针
BTree p;
p=(BTree)malloc(sizeof(BTNode));
p->node[0].ptr=T;
T=p;
if(T->node[0].ptr)
T->node[0].ptr->parent=T;
T->parent=NULL;
T->keynum=1;
T->node[1].key=r->key;
T->node[1].recptr=r;
T->node[1].ptr=ap;
if(T->node[1].ptr)
T->node[1].ptr->parent=T;
}//NewRoot
void split(BTree &q,BTree &ap)
{// 将结点q分裂成两个结点,前一半保留,后一半移入新生结点ap
int i,s=(m+1)/2;
ap=(BTree)malloc(sizeof(BTNode));//生成新结点ap
ap->node[0].ptr=q->node[s].ptr;//原来结点中间位置关键字相应指针指向的子树放到
//新生成结点的0棵子树中去
for(i=s+1;i<=m;i++) //后一半移入ap
{
ap->node[i-s]=q->node[i];
if(ap->node[i-s].ptr)
ap->node[i-s].ptr->parent=ap;
}//for
ap->keynum=m-s;
ap->parent=q->parent;
q->keynum=s-1; // q的前一半保留,修改keynum
}//split
void InsertBTree(BTree &T,Record *r,BTree q,int i)
{//在m阶B树T上结点*q的key[i]与key[i+1]之间插入关键字K的指针r。若引起
// 结点过大,则沿双亲链进行必要的结点分裂调整,使T仍是m阶B树。
BTree ap=NULL;
int finished=false;
int s;
Record *rx;
rx=r;
while(q&&!finished)
{
Insert(q,i,rx,ap);//将r->key、r和ap分别插入到q->key[i+1]、
//q->recptr[i+1]和q->ptr[i+1]中
if(q->keynum<m)
finished=true;
else
{//分裂结点*q
s=(m+1)/2;
rx=q->node[s].recptr;
split(q,ap);//将q->key[s+1..m],q->ptr[s..m]和q->recptr[s+1..m]
//移入新结点*ap
q=q->parent;
if(q)
i=Search(q,rx->key);//在双亲结点*q中查找rx->key的插入位置
}//else
}//while
if(!finished) //T是空树(参数q初值为NULL)或根结点已分裂为
//结点*q和*ap
NewRoot(T,rx,ap);
}//InsertBTree
Result SearchBTree(BTree T,KeyType K)
{// 在m阶B树T上查找关键字K,返回结果(pt,i,tag)。若查找成功,则特征值
// tag=1,指针pt所指结点中第i个关键字等于K;否则特征值tag=0,等于K的
// 关键字应插入在指针Pt所指结点中第i和第i+1个关键字之间。
BTree p=T,q=NULL; //初始化,p指向待查结点,q指向p的双亲
int found=false;
int i=0;
Result r;
while(p&&!found)
{
i=Search(p,K);//p->node[i].key≤K<p->node[i+1].key
if(i>0&&p->node[i].key==K)
found=true;
else
{
q=p;
p=p->node[i].ptr;//在子树中继续查找
}//else
}//while
r.i=i;
if(found)
{
r.pt=p;
r.tag=1;
}//if
else
{
r.pt=q;
r.tag=0;
}//else
return r;
}//SearchBTree
void print(BTNode c,int i) // TraverseDSTable()调用的函数
{
printf("(%d,%s)",c.node[i].key,c.node[i].recptr->others.info);
void TraverseDSTable(BTree DT,void(*Visit)(BTNode,int))
{// 初始条件: 动态查找表DT存在,Visit是对结点操作的应用函数
// 操作结果: 按关键字的顺序对DT的每个结点调用函数Visit()一次且至多一次
int i;
if(DT) //非空树
{
if(DT->node[0].ptr) // 有第0棵子树
TraverseDSTable(DT->node[0].ptr,Visit);
for(i=1;i<=DT->keynum;i++)
{
Visit(*DT,i);
if(DT->node[i].ptr) // 有第i棵子树
TraverseDSTable(DT->node[i].ptr,Visit);
}//for
}//if
}//TraverseDSTable
void InputBR(BTree &t,Record r[])
{
Result s;
for(int i=0;i<N;i++)
{
s=SearchBTree(t,r[i].key);
if(!s.tag)
InsertBTree(t,&r[i],s.pt,s.i);
}
}//InputBR
void UserSearch(BTree t)
{
int i;
Result s;
printf("\n请输入待查找记录的关键字: ");
scanf("%d",&i);
s=SearchBTree(t,i);
if(s.tag)
print(*(s.pt),s.i);
else
printf("没找到");
printf("\n");
}//UserSearch
void DeleteIt(BTree t,BTNode *dnode,int id)
{
if(dnode->keynum>=ceil(m/2))
{
dnode->keynum--;
dnode->node[id].ptr=NULL;
}//if被删关键字Ki所在结点的关键字数目不小于ceil(m/2),则只需从结点中删除Ki和相应指针Ai,树的其它部分不变。
else if((dnode->keynum==(ceil(m/2)-1))&&((id+1)<(m-1))&&dnode->parent->node[id+1].ptr->keynum>(ceil(m/2)-1))
{
for(int i=1;i<m&&dnode->parent->node[i].key < dnode->parent->node[id+1].ptr->node[1].key;i++)
dnode->node[i].key=dnode->parent->node[i].key;
dnode->parent->node[1].key=dnode->parent->node[id+1].ptr->node[1].key;
(dnode->parent->node[id+1].ptr->keynum)--;
}//else if 被删关键字Ki所在结点的关键字数目等于ceil(m/2)-1,则需调整。本次为与右兄弟调整
else if((dnode->keynum==(ceil(m/2)-1))&&((id-1)>0 )&&dnode->parent->node[id-1].ptr->keynum>(ceil(m/2)-1))
{
for(int i=1;i<m&&dnode->parent->node[i].key > dnode->parent->node[id-1].ptr->node[dnode->parent->node[id-1].ptr->keynum].key;i++)
dnode->node[i].key=dnode->parent->node[i].key;
dnode->parent->node[1].key=dnode->parent->node[id-1].ptr->node[dnode->parent->node[id-1].ptr->keynum].key;
(dnode->parent->node[id-1].ptr->keynum)--;
}//2-else if被删关键字Ki所在结点的关键字数目等于ceil(m/2)-1,则需调整。本次为与左兄弟调整
else if((dnode->keynum==(ceil(m/2)-1))&&((id+1)<(m-1))&&dnode->parent->node[id+1].ptr->keynum==(ceil(m/2)-1))
{
do
{
BTree tmp;
tmp=dnode;
dnode->parent->node[id+1].ptr->node[2]=dnode->parent->node[id+1].ptr->node[1];
dnode->parent->node[id+1].ptr->node[1]=dnode->parent->node[1];
dnode->parent->node[id+1].ptr->keynum++;
dnode->parent->node[id+1].ptr->node[0].ptr=dnode->node[1].ptr;
dnode->parent->keynum--;
dnode->parent->node[id].ptr=NULL;
tmp=dnode;
if(dnode->parent->keynum>=(ceil(m/2)-1))
dnode->parent->node[1]=dnode->parent->node[2];
dnode=dnode->parent;
free(tmp);
}while(dnode->keynum<(ceil(m/2)-1)); //双亲中keynum<
}//3-else if被删关键字Ki所在结点和其相邻兄弟结点中的的关键字数目均等于ceil(m/2)-1,本次假设右兄弟存在
else if((dnode->keynum==(ceil(m/2)-1))&&(id-1)>0 &&dnode->parent->node[id-1].ptr->keynum==(ceil(m/2)-1))
{
do
{
BTree tmp;
tmp=dnode;
dnode->parent->node[id-1].ptr->node[2]=dnode->parent->node[id-1].ptr->node[1];
dnode->parent->node[id-1].ptr->node[1]=dnode->parent->node[1];
dnode->parent->node[id-1].ptr->keynum++;
dnode->parent->node[id-1].ptr->node[0].ptr=dnode->node[1].ptr;
dnode->parent->keynum--;
dnode->parent->node[id].ptr=NULL;
tmp=dnode;
if(dnode->parent->keynum>=(ceil(m/2)-1))
dnode->parent->node[1]=dnode->parent->node[2];
dnode=dnode->parent;
free(tmp);
}while(dnode->keynum<(ceil(m/2)-1)); //双亲中keynum<
}//4-else if被删关键字Ki所在结点和其相邻兄弟结点中的的关键字数目均等于ceil(m/2)-1,本次假设左兄弟存在
else printf("Error!"); //出现异常
}//DeleteIt
void UserDelete(BTree t)
{
KeyType date;
Result s;
printf("Please input the date you want to delete:\n");
scanf("%d",&date);
s=SearchBTree(t,date);
if(!s.tag) printf("Search failed,no such date\n");
else DeleteIt(t,s.pt,s.i);
}//UserDelete
int main()
{
Record r[N]={{24,"1"},{45,"2"},{53,"3"},{12,"4"},{37,"5"},
{50,"6"},{61,"7"},{90,"8"},{100,"9"},{70,"10"},
{3,"11"},{30,"12"},{26,"13"},{85,"14"},{3,"15"},
{7,"16"}};
BTree t;
InitDSTable(t);
InputBR(t,r);
printf("按关键字的顺序遍历B_树:\n");
TraverseDSTable(t,print);
UserSearch(t);
UserDelete(t);
TraverseDSTable(t,print);
DestroyDSTable(t);
return 1;
}
七、复杂度分析
B-树查找包含两种基本动作:
●在B-树上查找结点
●在结点中找关键字
前一操作在磁盘上进行,后一操作在内存进行。因此查找效率主要由前一操作决定。在磁盘上查找的次数取决于关键字结点在B-树上的层次数。
定理:若n≥1,m≥3,则对任意一棵具有n个关键字的m阶B-树,其树高度h至多为logt((n+1)/2)+1,t= ceil(m/2)。也就是说根结点到关键字所在结点的路径上涉及的结点数不超过logt((n+1)/2)+1。推理如下:
B+树
B+ 树是一种树数据结构,是一个n叉树,每个节点通常有多个孩子,一颗B+树包含根节点、内部节点和叶子节点。根节点可能是一个叶子节点,也可能是一个包含两个或两个以上孩子节点的节点。
一、定义
二、B+树的查找、插入和删除
B+树的查找
Function: search (k)
return
tree_search (k, root); Function: tree_search (k, node)
if
node is a leaf then
return
node;
switch
k
do
case
k < k_0
return
tree_search(k, p_0);
case
k_i ≤ k < k_{i+
1
}
return
tree_search(k, p_{i+
1
});
case
k_d ≤ k
return
tree_search(k, p_{d+
1
});
//伪代码假设没有重复值
B+树的插入
B+树的删除
三、B+树与B树的区别
- 所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。(而B 树的叶子节点并没有包括全部需要查找的信息)
- 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。(而B 树的非终节点也包含需要查找的有效信息)
四、B+树与操作系统的文件索引和数据库索引
B+树的磁盘读写代价更低
- B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B+树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。
- B+树的查询效率更加稳定由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
红黑树
二. 数据结构定义
RBT数据结构在基本二叉树数据结构之上增加一个color和parent,color用于保存节点颜色,parent指向父节点。
- #define COLOR_RED 0
- #define COLOR_BLACK 1
- typedef int keyType;
- // 定义而二叉树节点数据结构
- struct BinaryTreeNode {
- keyType key;
- int color;
- BinaryTreeNode* parent; // 保存父节点
- BinaryTreeNode* left; // left child
- BinaryTreeNode* right; // right child
- };
- // define red-black tree node
- typedef BinaryTreeNode rbnode;
- // define red-black tree
- typedef BinaryTreeNode rbtree;
三、 插入Key
无论怎么样操作,性质1和性质3是始终能够保持的。新插入节点的时候,新节点的初始颜色为红色,这样可以不直接破坏性质5,这个时候可能性质4受到威胁,需要调整节点颜色或这左一些旋转等操作。假设新插入的节点为N,其父节点为P,祖父节点为G,叔父节点为U,下面具体分析一下插入新节点的各种情况。
情形1 、空树
当树为空的时候,直接将N节点设为黑色作为树的根节点返回。
情形2 、P为黑色节点
图中所示为N插入到P左孩子节点中,这个过程完全满足性质 1 - 5 的要求,并没有破坏 RBT 的规则,因此,此时即可停止检查,插入过程结束。
同理,若P为黑色节点,N插入后作为P的右孩子节点也不会破坏 RBT的规则。
(下面开始讨论 P 为红色 的情形,由 性质2 推导出 G 一定存在,根据性质 4,G一定是黑色)
情形3 、P为红色节点,U存在且为红色节点
这种情况下,将G变为红色,P和U变为黑色,这样维持了G为Root的子树内性质4和5,然后以G作为新的插入节点,从情形1开始迭代。情形4 、P为红色节点,U不存在或者U为黑色
(a) 若P在G的左侧,插入点N也在P的左侧 ------ LL 型假设G的右侧有 路径有 x个黑色节点,则c有x个黑色节点,G为root的子树路径有x+1个黑色节点。 此时, 只需要以P为中心右旋,将P变为黑色,G变为红色,G的左子树替换为c,这样就可以继续保证以P为root的子树有x+1个黑色节点,检查停止。(b)若P在G的左侧,N在P的右侧 -----LR型
这时候先将节点P和节点N做调整,进行左旋,变化成(a)的形态,然后做一次右旋。到此,调整完毕。图略(c)若P在G的右侧,N在P的右侧 ----------RR型
情形和(a)正好相反,做一次左旋即可。图略(d)若P在G的右侧,N在P的左侧 ----------RL型
情形和(b)正好相反,先做做一次右旋,后进行一次左旋即可。
RBT插入算法过程代码如下:
- // 向右旋转
- void rb_right_rotate(rbnode* g) {
- rbnode * p = g->left;
- keyType k = g->key;
- g->key = p->key;
- p->key = k;
- g->left = p->left;
- if (NULL != p->left) {
- p->left->parent = g;
- }
- p->left = p->right;
- p->right = g->right;
- if (NULL != g->right) {
- g->right->parent = p;
- }
- g->right = p;
- }
- // 向左旋转
- void rb_left_rotate(rbnode* g) {
- rbnode* p = g->right;
- keyType k = g->key;
- g->key = p->key;
- p->key = k;
- g->right = p->right;
- if (NULL != p->right) {
- p->right->parent = g;
- }
- p->right = p->left;
- p->left = g->left;
- if (NULL != g->left) {
- g->left->parent = p;
- }
- g->left = p;
- }
- // check and adjust after insertion
- void rbt_insert_check(rbnode* node) {
- // CASE 1 : if the node equals the root
- // set the color of the node black and return.
- if (NULL == node->parent) {
- node->color = COLOR_BLACK;
- return;
- }
- // CASE 2 : when the parent of the node is black
- // All features have been met, stop check and return
- if (node->parent->color == COLOR_BLACK) {
- return;
- }
- // Otherwise, the the parent node is RED, and this means the grandfather node exists.
- rbnode* gf = node->parent->parent;
- rbnode* uf = (gf->left == node->parent) ? gf->right : gf->left;
- // CASE 3 : When the uncle node exists and it's RED
- if (NULL != uf && uf->color == COLOR_RED) {
- // set parent and uncle black, set grandfather red
- node->parent->color = COLOR_BLACK;
- uf->color = COLOR_BLACK;
- gf->color = COLOR_RED;
- // then re check the tree at grandfather node from CASE 1.
- rbt_insert_check(gf);
- return;
- }
- // CASE 4 : when the uncle is NULL or its color is Black.
- if (node->parent == gf->left) { // the node in the left of its grandfather
- // (a) LL model
- if (node == node->parent->left) { // the node in the left of its parent
- rb_right_rotate(gf);
- }
- // (b) LR model
- else if (node == node->parent->right) { //the node in the right of its parent.
- rb_left_rotate(node->parent);
- rb_right_rotate(gf);
- }
- } else if (node->parent == gf->right) { //the node in the right of its grandfather
- // (c) RR model
- if (node == node->parent->right) { //the node in the right of its parent.
- rb_left_rotate(gf);
- }
- // (d) RL model
- else if (node == node->parent->left) { //the node in the left of its parent.
- rb_right_rotate(node->parent);
- rb_left_rotate(gf);
- }
- }
- }
- // 插入新的关键字
- int rbt_insert(rbtree* &tree, keyType key) {
- if (NULL == tree) { // if the tree is NULL
- tree = (rbtree*) malloc((sizeof(rbnode)));
- tree->key = key;
- tree->color = COLOR_BLACK;
- tree->parent = tree->left = tree->right = NULL;
- return 1;
- }
- // find insert point
- rbnode *n = tree, *p = tree->parent;
- while (NULL != n) {
- if (key == n->key) {
- return 0;
- }
- p = n;
- n = (key > p->key) ? p->right : p->left;
- }
- // insert the node
- n = (rbtree*) malloc((sizeof(rbnode)));
- n->key = key;
- n->color = COLOR_RED;
- n->parent = p;
- n->right = n->left = NULL;
- ((key > p->key) ? p->right : p->left) = n;
- // adjust the tree
- rbt_insert_check(n);
- return 1;
- }
四、删除Key
从RBT中删除指定的Key时,需要重新调整树的形态使之满足红黑树的特性。下面来具体分析一下删除Key的过程。
(1)根据key找到要删除的节点Node:如果没有找到,直接返回,否则,进行下一步操作;
(2)如果Node有两个孩子节点,那么删除Node之后该如何放置其孩子节点呢?
这个时候需要进行预处理,转化为删除只有一个孩子的节点的情形。
找到Node的中序前驱(后继)节点,将前驱(后继)节点的值复制到Node中,Node指向前驱(后继)节点;
(3)到此步骤,Node确切的指示为待删除的节点,且Node最多只有一个孩子节点。
删除Node节点,将Node的孩子节点顶替Node的位置.(注意Node为Root的情形)
(4)调整RBT树使其满足规定的5大特性。假设上一步中顶替上来的节点为 N ,其父节点为 P ,其兄弟节点为 S ,Sl 和 Sr 分别为 S 的左右孩子节点 ,假设 N 在 P 的左侧, 调整过程如下:
(右侧与左侧对称,这里分析一种即可)
情形1 、N节点为红色
当N节点为红色的时候,由于左侧缺少一个黑色的节点,可以将N节点的颜色修改为黑色,这样即可从新满足性质5.调整完毕。
情形2、S节点为红色
当S节点为红色节点时,则可以将P节点向左旋转,旋转之后P为红色,S为黑色,这个时候S-Sl这条简单路径黑色节点数目合法,S-P-Sl节点数目也合法,S-P-N路径黑色节点数目少一个。相当于,P的左侧删除了一个黑色节点,应当重新调整 P,S,Sl,Sr所指向的节点,进行后续操作。
后续可能的情形为:3,5,6
情形3、P节点为红色,S,Sl,Sr为黑色
当P为红色,S为黑色,Sl和Sr均为黑色的时候,则可以简单的交换P节点和S节点的颜色,这样即可使各条简单路径的黑色节点数目和删除节点前相等。调整完毕。
情形4、P,S,Sl,Sr均为黑色
当P、S、Sl、Sr均为黑色节点的时候,只需要简单的将S节点标记为红色,这样以P节点为根的个简单路径黑色节点数目比删除之前少一个。因此,P相当与N的位置,从P的父节点开始递归进行调整。
(如果此时P为树的根节点,即可停止调整)
情形5、Sl为红色节点并且Sr为黑色
这种情况下,可以将Sl进行右旋操作,右旋之后,Sl为黑色,S为红色,Sr不变,这样保持P节点右子树中各简单路径黑色节点数目和旋转前一样。这个时候,原来的S相当于Sl的位置,Sl相当与a,Sr相当与S。
更新S,Sl,Sr新指向的位置,进行下一步操作。
后续情形为:6.
情形6、Sr为红色
这时,将P节点做一次左旋操作,将Sr的颜色设置为黑色,P和S交换颜色,调整之后,各简单路径的黑色节点数目和删除节点之前一样。此时调整结束。
- int is_black(rbnode * node) {
- if (node == NULL) return 1;
- if (node->color == COLOR_BLACK) return 1;
- return 0;
- }
- // check and adjust after deletion
- void rbt_delete_check(rbnode* p, bool delLeft) {
- rbnode * n = delLeft ? p->left : p->right;
- // case 1: n is red
- if (NULL != n && n->color == COLOR_RED) {
- n->color = COLOR_BLACK;
- return;
- }
- // else the other subtree of p at least has one more node.
- rbnode * s = delLeft ? p->right : p->left;
- rbnode * sl = s->left;
- rbnode * sr = s->right;
- // case 2 : S is red , p left rotate
- if (s->color == COLOR_RED) {
- if (delLeft) {
- rb_left_rotate(p);
- } else {
- rb_right_rotate(p);
- }
- p = s;
- s = delLeft ? sl : sr;
- sl = s->left;
- sr = s->right;
- }
- // Other cases : S is black
- // when SL and SR are black
- if (is_black(sl) && is_black(sr)) {
- // case 3 : P is red, S SL and SR are black
- if (!is_black(p)) {
- p->color = COLOR_BLACK;
- s->color = COLOR_RED;
- }
- // case 4: P ,S, SL and SR are black
- else {
- s->color = COLOR_RED;
- if (NULL == p->parent) {
- return;
- }
- delLeft = (p == p->parent->left);
- rbt_delete_check(p->parent, delLeft);
- }
- return;
- }
- // when SL and SR has red node
- if (delLeft) {
- if (is_black(sr)) { // case 5(a) : delLeft is true and SR is black
- rb_right_rotate(s);
- sr = s->right;
- }
- rb_left_rotate(p); // case 6(a) : rotate the p node
- sr->color = COLOR_BLACK;
- } else {
- if (is_black(sl)) { // case 5(b) : delLeft is false and SL is black
- rb_left_rotate(s);
- sl = s->left;
- }
- rb_right_rotate(p); // case 6(b) : rotate the p node
- sl->color = COLOR_BLACK;
- }
- }
- // delete a key from the RBT
- int rbt_delete(rbtree* &tree, keyType key) {
- if (NULL == tree) {
- return 0;
- }
- // find the node
- rbnode *curr, *temp;
- for (curr = tree;;) {
- if (key == curr->key) {
- break;
- }
- curr = (key > curr->key) ? curr->right : curr->left;
- if (NULL == curr) {
- return 0;
- }
- }
- // if the node to delete has two children
- if (NULL != curr->left && NULL != curr->right) {
- for (temp = curr->left; NULL != temp->right; temp = temp->right) {
- }
- curr->key = temp->key;
- curr = temp;
- }
- if (NULL == curr->parent) { // it is the tree root
- tree = (NULL == curr->left) ? curr->right : curr->left;
- if (tree != NULL) {
- tree->color = COLOR_BLACK;
- tree->parent = NULL;
- }
- free(curr);
- return 1;
- }
- // delete the node
- rbnode* fa = curr->parent;
- temp = (NULL == curr->left) ? curr->right : curr->left;
- bool delLeft = (fa->left == curr);
- if (NULL != temp) {
- temp->parent = fa;
- }
- delLeft ? fa->left = temp : fa->right = temp;
- if (curr->color != COLOR_RED) { // adjust after deletion
- rbt_delete_check(fa, delLeft);
- }
- free(curr);
- return 1;
- }
- B,B-,B+,B*树
- B-、B、B+、B*树
- B/B+/B*树
- B- ,B+ , B*树
- B , B+ ,B*树
- B-, B+,B* 树
- B树、B-树、B+树、B*树 红黑树
- B树、B-树、B+树、B*树 红黑树
- B树、B-树、B+树、B*树 红黑树
- B树、B-树、B+树、B*树 红黑树
- B树、B-树、B+树、B*树 红黑树
- B树、B-树、B+树、B*树 红黑树
- B树、B-树、B+树、B*树 红黑树
- B树、B-树、B+树、B*树 红黑树
- B树、B-树、B+树、B*树 红黑树
- B(B-)树, B+树,B*树,红黑树
- B-树、B+树、红黑树
- 红黑树,b-树,b+树
- 使用Spring处理自定义注解
- 112 指数运算
- 变形课(并查集)
- javascript跨域访问探索之旅
- Lua和C++交互总结(很详细)
- B-树、B+树、红黑树
- 国外别墅后期PS教程洛阳生
- 淘宝分布式配置管理服务Diamond
- Django项目国际化
- mybatis generator自动生成代码时 只生成了insert 而没有其他的
- linux下查看cpu物理个数和逻辑个数
- 在XCode中使用SVN
- 微信公众号可通过现金红包接口发放微信支付现金红包(附开发教程)
- 深入理解Java的接口和抽象类