树
树状图是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
每个节点有零个或多个子节点;没有父节点的节点称为根节点;每一个非根节点有且只有一个父节点;
除了根节点外,每个子节点可以分为多个不相交的子树;[1]
中文名 树 外文名 tree 数据结构 基本数据结构的一种
目录
1 定义
2 相关术语
3 种类
4 深度
5 表示方法
▪ 图像表达法
▪ 符号表达法
▪ 遍历表达法
6 其他
7 父节点表示法
▪ 存储结勾
▪ 基本操作
▪ 构造空树
▪ 构造树
▪ 判断树是否为空
▪ 获取树的深度
▪ 获取第i个节点的值
▪ 改变节点的值
▪ 获取节点的父节点
▪ 获取节点的最左孩子节点
▪ 获取节点的右兄弟节点
▪ 输出树
▪ 向树中插入另一棵树
▪ 删除子树
▪ 层序遍历树
8 孩子链表表示法
定义
树
树 (7张)
树(tree)是包含n(n>0)个结点的有穷集,其中:
(1)每个元素称为结点(node);
(2)有一个特定的结点被称为根结点或树根(root)。
(3)除根结点之外的其余数据元素被分为m(m≥0)个互不相交的集合T1,T2,……Tm-1,其中每一个
集合Ti(1<=i<=m)本身也是一棵树,被称作原树的子树(subtree)。
树也可以这样定义:树是由根结点和若干颗子树构成的。树是由一个集合以及在该集合上定义的一种关
系构成的。集合中的元素称为树的结点,所定义的关系称为父子关系。父子关系在树的结点之间建立了
一个层次结构。在这种层次结构中有一个结点具有特殊的地位,这个结点称为该树的根结点,或称为树
根。
我们可以形式地给出树的递归定义如下:
单个结点是一棵树,树根就是该结点本身。
设T1,T2,..,Tk是树,它们的根结点分别为n1,n2,..,nk。用一个新结点n作为n1,n2,..,nk的父亲,则得
到一棵新树,结点n就是新树的根。我们称n1,n2,..,nk为一组兄弟结点,它们都是结点n的子结点。我们
还称T1,T2,..,Tk为结点n的子树。
空集合也是树,称为空树。空树中没有结点。
相关术语
节点的度:一个节点含有的子树的个数称为该节点的度;
叶节点或终端节点:度为0的节点称为叶节点;
非终端节点或分支节点:度不为0的节点;
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
兄弟节点:具有相同父节点的节点互称为兄弟节点;
树的度:一棵树中,最大的节点的度称为树的度;
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次;
堂兄弟节点:双亲在同一层的节点互为堂兄弟;
节点的祖先:从根到该节点所经分支上的所有节点;
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
森林:由m(m>=0)棵互不相交的树的集合称为森林;
种类
无序树:树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树;
有序树:树中任意节点的子结点之间有顺序关系,这种树称为有序树;
二叉树:每个节点最多含有两个子树的树称为二叉树;
完全二叉树
满二叉树
霍夫曼树:带权路径最短的二叉树称为哈夫曼树或最优二叉树;
深度
定义一棵树的根结点层次为1,其他节点的层次是其父结点层次加1。一棵树中所有结点的层次的最大值
称为这棵树的深度。
表示方法
图像表达法
树的表示方法有很多种,最常用的是图像表示法。
一下是一个普通的树(非二叉树):
符号表达法
用括号先将根结点放入一对圆括号中,然后把它的子树由左至右的顺序放入括号中,而对子树也采用同
样的方法处理;同层子树与它的根结点用圆括号括起来,同层子树之间用逗号隔开,最后用闭括号括起
来。如前文树形表示法可以表示为:(1(2(5(9,10)),3(6,7),4(8)))
遍历表达法
遍历表达法有3种方法:先序遍历、中序遍历、后序遍历[2]
例如右图:
其先序遍历为ABDECF
其中序遍历为DBEAFC
其后序遍历为DEBFCA
具体请参照参考资料
其他
关于二叉树的其他知识请参照参考资料。
父节点表示法
存储结勾
/* 树节点的定义 */
#define MAX_TREE_SIZE 100
typedef struct{
TElemType data;
int parent; /* 父节点位置域 */
} PTNode;
typedef struct{
PTNode nodes[MAX_TREE_SIZE];
int n; /* 节点数 */
} PTree;
基本操作
设已有链队列类型LinkQueue的定义及基本操作(参见队列)。[3]
构造空树
清空或销毁一个树也是同样的操作
void ClearTree(PTree *T){
T->n = 0;
}
构造树
void CreateTree(PTree *T){
LinkQueue q;
QElemType p,qq;
int i=1,j,l;
char c[MAX_TREE_SIZE]; /* 临时存放孩子节点数组 */
InitQueue(&q); /* 初始化队列 */
printf("请输入根节点(字符型,空格为空): ");
scanf("%c%*c",&T->nodes[0].data); /* 根节点序号为0,%*c吃掉回车符 */
if(T->nodes[0].data!=Nil) /* 非空树 */ {
T->nodes[0].parent=-1; /* 根节点无父节点 */
qq.name=T->nodes[0].data;
qq.num=0;
EnQueue(&q,qq); /* 入队此节点 */
while(i<MAX_TREE_SIZE&&!QueueEmpty(q)) /* 数组未满且队不空 */ {
DeQueue(&q,&qq); /* 节点加入队列 */
printf("请按长幼顺序输入节点%c的所有孩子: ",qq.name);
gets(c);
l=strlen(c);
for(j=0;j<l;j++){
T->nodes[i].data=c[j];
T->nodes[i].parent=qq.num;
p.name=c[j];
p.num=i;
EnQueue(&q,p); /* 入队此节点 */
i++;
}
}
if(i>MAX_TREE_SIZE){
printf("节点数超过数组容量\n");
exit(OVERFLOW);
}
T->n=i;
}
else
T->n=0;
}
判断树是否为空
Status TreeEmpty(PTree *T){
/* 初始条件:树T存在。操作结果:若T为空树,则返回TRUE,否则返回FALSE */
return T->n==0;
}
获取树的深度
int TreeDepth(PTree *T){
/* 初始条件:树T存在。操作结果:返回T的深度 */
int k,m,def,max=0;
for(k=0;k<T->n;++k){
def=1; /* 初始化本节点的深度 */
m=T->nodes[k].parent;
while(m!=-1){
m=T->nodes[m].parent;
def++;
}
if(max<def)
max=def;
}
return max; /* 最大深度 */
}
获取根节点
TElemType Root(PTree *T){
/* 初始条件:树T存在。操作结果:返回T的根 */
int i;
for(i=0;i<T->n;i++)
if(T->nodes[i].parent<0)
return T->nodes[i].data;
return Nil;
}
获取第i个节点的值
TElemType Value(PTree *T,int i){
/* 初始条件:树T存在,i是树T中节点的序号。操作结果:返回第i个节点的值 */
if(i<T->n)
return T->nodes[i].data;
else
return Nil;
}
改变节点的值
[4]
1
Status Assign(PTree *T,TElemType cur_e,TElemType value){ /* 初始条件:树T存在,cur_e是树T中
节点的值。操作结果:改cur_e为value */ int j; for(j=0;j<T->n;j++) { if(T->nodes
[j].data==cur_e) { T->nodes[j].data=value; return OK; } } return ERROR;}
获取节点的父节点
1
TElemType Parent(PTree *T,TElemType cur_e){ /* 初始条件:树T存在,cur_e是T中某个节点 */ /*
操作结果:若cur_e是T的非根节点,则返回它的父节点,否则函数值为"空"*/ int j; for(j=1;j<T-
>n;j++) /* 根节点序号为0 */ if(T->nodes[j].data==cur_e) return T->nodes[T->nodes
[j].parent].data; return Nil;}
获取节点的最左孩子节点
1
TElemType LeftChild(PTree *T,TElemType cur_e){ /* 初始条件:树T存在,cur_e是T中某个节点 */
/* 操作结果:若cur_e是T的非叶子节点,则返回它的最左孩子,否则返回"空"*/ int i,j; for
(i=0;i<T->n;i++) if(T->nodes[i].data==cur_e) /* 找到cur_e,其序号为i */ break;
for(j=i+1;j<T->n;j++) /* 根据树的构造函数,孩子的序号>其父节点的序号 */ if(T->nodes
[j].parent==i) /* 根据树的构造函数,最左孩子(长子)的序号<其它孩子的序号 */ return T-
>nodes[j].data; return Nil;}
获取节点的右兄弟节点
1
TElemType RightSibling(PTree *T,TElemType cur_e){ /* 初始条件:树T存在,cur_e是T中某个节点
*/ /* 操作结果:若cur_e有右(下一个)兄弟,则返回它的右兄弟,否则返回"空"*/ int i; for
(i=0;i<T->n;i++) if(T->nodes[i].data==cur_e) /* 找到cur_e,其序号为i */ break; if
(T->nodes[i+1].parent==T->nodes[i].parent) /* 根据树的构造函数,若cur_e有右兄弟的话则右兄
弟紧接其后 */ return T->nodes[i+1].data; return Nil;}
输出树
1
void Print(PTree *T){ /* 输出树T。加 */ int i; printf("节点个数=%d\n",T->n); printf(" 节
点 父节点\n"); for(i=0;i<T->n;i++) { printf(" %c",Value(T,i)); /* 节点 */ if(T-
>nodes[i].parent>=0) /* 有父节点 */ printf(" %c",Value(T,T->nodes[i].parent)); /*
父节点 */ printf("\n"); }}
向树中插入另一棵树
1
Status InsertChild(PTree *T,TElemType p,int i,PTree c){ /* 初始条件:树T存在,p是T中某个节
点,1≤i≤p所指节点的度+1,非空树c与T不相交 */ /* 操作结果:插入c为T中p节点的第i棵子树 */
int j,k,l,f=1,n=0; /* 设交换标志f的初值为1,p的孩子数n的初值为0 */ PTNode t; if(!
TreeEmpty(T)) /* T不空 */ { for(j=0;j<T->n;j++) /* 在T中找p的序号 */ if(T->nodes
[j].data==p) /* p的序号为j */ break; l=j+1; /* 如果c是p的第1棵子树,则插在j+1处
*/ if(i>1) /* c不是p的第1棵子树 */ { for(k=j+1;k<T->n;k++) /* 从j+1开始找p的前
i-1个孩子 */ if(T->nodes[k].parent==j) /* 当前节点是p的孩子 */ { n+
+; /* 孩子数加1 */ if(n==i-1) /* 找到p的第i-1个孩子,其序号为k1 */
break; } l=k+1; /* c插在k+1处 */ } /* p的序号为j,c插在l处 */ if(l<T->n)
/* 插入点l不在最后 */ for(k=T->n-1;k>=l;k--) /* 依次将序号l以后的节点向后移c.n个位置
*/ { T->nodes[k+c.n]=T->nodes[k]; if(T->nodes[k].parent>=l) T-
>nodes[k+c.n].parent+=c.n; } for(k=0;k<c.n;k++) { T->nodes[l
+k].data=c.nodes[k].data; /* 依次将树c的所有节点插于此处 */ T->nodes[l
+k].parent=c.nodes[k].parent+l; } T->nodes[l].parent=j; /* 树c的根节点的父节点为p */
T->n+=c.n; /* 树T的节点数加c.n个 */ while(f) { /* 从插入点之后,将节点仍按层序排列
*/ f=0; /* 交换标志置0 */ for(j=l;j<T->n-1;j++) if(T->nodes[j].parent>T-
>nodes[j+1].parent) {/* 如果节点j的父节点排在节点j+1的父节点之后(树没有按层序排列)
,交换两节点*/ t=T->nodes[j]; T->nodes[j]=T->nodes[j+1]; T-
>nodes[j+1]=t; f=1; /* 交换标志置1 */ for(k=j;k<T->n;k++) /* 改变父节点序
号 */ if(T->nodes[k].parent==j) T->nodes[k].parent++; /* 父节点序号
改为j+1 */ else if(T->nodes[k].parent==j+1) T->nodes[k].parent--;
/* 父节点序号改为j */ } } return OK; } else /* 树T不存在 */ return
ERROR;}
删除子树
1
Status deleted[MAX_TREE_SIZE+1]; /* 删除标志数组(全局量) */void DeleteChild(PTree
*T,TElemType p,int i){ /* 初始条件:树T存在,p是T中某个节点,1≤i≤p所指节点的度 */ /* 操
作结果:删除T中节点p的第i棵子树 */ int j,k,n=0; LinkQueue q; QElemType pq,qq; for
(j=0;j<=T->n;j++) deleted[j]=0; /* 置初值为0(不删除标记) */ pq.name='a'; /* 此成员不用
*/ InitQueue(&q); /* 初始化队列 */ for(j=0;j<T->n;j++) if(T->nodes[j].data==p)
break; /* j为节点p的序号 */ for(k=j+1;k<T->n;k++) { if(T->nodes[k].parent==j) n+
+; if(n==i) break; /* k为p的第i棵子树节点的序号 */ } if(k<T->n) /* p的第i棵子树节
点存在 */ { n=0; pq.num=k; deleted[k]=1; /* 置删除标记 */ n++; EnQueue
(&q,pq); while(!QueueEmpty(q)) { DeQueue(&q,&qq); for(j=qq.num+1;j<T->n;j+
+) if(T->nodes[j].parent==qq.num) { pq.num=j; deleted[j]=1;
/* 置删除标记 */ n++; EnQueue(&q,pq); } } for(j=0;j<T->n;j+
+) if(deleted[j]==1) { for(k=j+1;k<=T->n;k++) { deleted
[k-1]=deleted[k]; T->nodes[k-1]=T->nodes[k]; if(T->nodes[k].parent>j)
T->nodes[k-1].parent--; } j--; } T->n-=n; /* n为待删除节点数
*/ }}
层序遍历树
1
void TraverseTree(PTree *T,void(*Visit)(TElemType)){ /* 初始条件:二叉树T存在,Visit是对节点
操作的应用函数 */ /* 操作结果:层序遍历树T,对每个节点调用函数Visit一次且仅一次 */ int i;
for(i=0;i<T->n;i++) Visit(T->nodes[i].data); printf("\n");}
1
孩子链表表示法
存储结构[5]
1
/*树的孩子链表存储表示*/typedef struct CTNode { // 孩子节点 int child; struct CTNode
*next;} *ChildPtr;typedef struct { ElemType data; // 节点的数据元素 ChildPtr
firstchild; // 孩子链表头指针} CTBox;typedef struct { CTBox nodes[MAX_TREE_SIZE]; int
n, r; // 节点数和根节点的位置} CTree;
========
B树、B-树、B+树、B*树
B树即二叉搜索树:
1.所有非叶子结点至多拥有两个儿子(Left和Right);
2.所有结点存储一个关键字;
3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树;
如:
B树的搜索,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中;
否则,如果查询关键字比结点关键字小,就进入左儿子;如果比结点关键字大,就进入
右儿子;如果左儿子或右儿子的指针为空,则报告找不到相应的关键字;
如果B树的所有非叶子结点的左右子树的结点数目均保持差不多(平衡),那么B树
的搜索性能逼近二分查找;但它比连续内存空间的二分查找的优点是,改变B树结构
(插入与删除结点)不需要移动大段的内存数据,甚至通常是常数开销;
如:
但B树在经过多次插入与删除后,有可能导致不同的结构:
右边也是一个B树,但它的搜索性能已经是线性的了;同样的关键字集合有可能导致不同的
树结构索引;所以,使用B树还要考虑尽可能让B树保持左图的结构,和避免右图的结构,也就
是所谓的“平衡”问题;
实际使用的B树都是在原B树的基础上加上平衡算法,即“平衡二叉树”;如何保持B树
结点分布均匀的平衡算法是平衡二叉树的关键;平衡算法是一种在B树中插入和删除结点的
策略;
B-树
是一种多路搜索树(并不是二叉的):
1.定义任意非叶子结点最多只有M个儿子;且M>2;
2.根结点的儿子数为[2, M];
3.除根结点以外的非叶子结点的儿子数为[M/2, M];
4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
5.非叶子结点的关键字个数=指向儿子的指针个数-1;
6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的
子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
8.所有叶子结点位于同一层;
如:(M=3)
B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果
命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为
空,或已经是叶子结点;
B-树的特性:
1.关键字集合分布在整颗树中;
2.任何一个关键字出现且只出现在一个结点中;
3.搜索有可能在非叶子结点结束;
4.其搜索性能等价于在关键字全集内做一次二分查找;
5.自动层次控制;
由于限制了除根结点以外的非叶子结点,至少含有M/2个儿子,确保了结点的至少
利用率,其最底搜索性能为:
其中,M为设定的非叶子结点最多子树个数,N为关键字总数;
所以B-树的性能总是等价于二分查找(与M值无关),也就没有B树平衡的问题;
由于M/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占
M/2的结点;删除结点时,需将两个不足M/2的兄弟结点合并;
B+树
B+树是B-树的变体,也是一种多路搜索树:
1.其定义基本与B-树同,除了:
2.非叶子结点的子树指针与关键字个数相同;
3.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树
(B-树是开区间);
5.为所有叶子结点增加一个链指针;
6.所有关键字都在叶子结点出现;
如:(M=3)
B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在
非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;
B+的特性:
1.所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好
是有序的;
2.不可能在非叶子结点命中;
3.非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储
(关键字)数据的数据层;
4.更适合文件索引系统;
B*树
是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针;
B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3
(代替B+树的1/2);
B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据
复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父
结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;
B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分
数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字
(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之
间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;
所以,B*树分配新结点的概率比B+树要低,空间使用率更高;
小结
B树:二叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于
走右结点;
B-树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键
字范围的子结点;
所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;
B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点
中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;
B*树:在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率
从1/2提高到2/3;
========
R树
R树是GUTTMAN于1984年提出的最早支持有序扩展的对象存取方法之一,也是目前应用最为广泛的一种空间索引结构。许多商用空间数据库系统,如MapInfo SpatialWaro和Oracle Spatial等均提供对R树的支
持,开放源码系统PostgreSQL也实现了R树。近二十多年来,许多学者致力于R树的研究,在R树的基础上
衍生出了许多变种。比较典型的有R+树、R*树、压缩R树等。
中文名 R树 外文名 R-Tree 提出者 Antonin Guttman 提出时间 1984年 应用学科 计算机 适用领域范
围 软件,数据结构 适用领域范围 多维索引
目录
1 定义
2 特点
3 操作
▪ 搜索
▪ 插入
▪ 删除
定义编辑
一棵R树满足如下的性质:
1.除根结点之外,所有非根结点包含有m至M个记录索引(条目)。根结点的记录个数可以少于m。通常,
m=M/2。
2.对于所有叶子中存储的记录(条目),I是最小的可以在空间中完全覆盖这些记录所代表的点的矩形(
注意:此处所说的“矩形”是可以扩展到高维空间的)。
3.对于所有非叶子结点上的记录(条目),i是最小的可以在空间上完全覆盖这些条目所代表的点的矩形
(同性质2)。
4.所有叶子结点都位于同一层,因此R树为平衡树。
特点编辑
R树是一个高度平衡树,它是B树在k维上的自然扩展,用空间对象的MBR来近似表达空间对象,根据地物
的MBR建立R树,可以直接对空间中占据一定范围的空间对象进行索引。R树的每一个结点都对应着磁盘页
D和区域I,如果结点不是叶结点,则该结点的所有子结点的区域都在区域I的范围之内,而且存储在磁盘
页D中。如果结点是叶结点,那么磁盘页D中存储的将是区域I范围内的一系列子区域,子区域紧紧围绕空
间对象,一般为空间对象的外接矩形。
R树中每个结点所能拥有的子结点数目是有上下限的。下限保证索引对磁盘空间的有效利用,子结点的数
目小于下限的结点将被删除,该结点的子结点将被分配到其他的结点中;设立上限是因为每一个结点只
对应一个磁盘页,如果某个结点要求的空间大于一个磁盘页,那么该结点就要被划分为两个新的结点,
原来结点的所有子结点将被分配到这两个新的结点中。令M为一个结点中记录数目的最大值,mSM/2为一
参数,说明一个节点记录的最小值,m可作为调节树结构的一个可变参数,R树满足如下几项特点:
1.根节点若非叶子节点,则至少有两个子节点;
2.每个非根叶节点和非叶节点包含的实体个数均介于m和M之间;
3.所有叶子节点在同一层次;
R树兄弟结点对应的空间区域可以重叠,可以较容易地进行插入和删除操作。但正因为区域之间有重叠,
空间索引可能要对多条路径进行搜索后才能得到最后的结果。当查找与给定的查询窗口相交的所有空间
对象时,空间搜索算法是从根结点开始,向下搜索相应的子树.算法递归遍历所有约束矩形与查询窗口
相交的子树,当到达叶结点时,边界矩形中的元素被取出并测试其是否与查询矩形相交,所有与查询窗
口相交的叶结点即为要查找的空间对象。R树的查询效率会因重叠区域的增大而大大减弱,在最坏情况下
,其时间复杂度甚至会由对数搜索退化成线性搜索。正是这个原因促使了R+树的产生。
在R+树中,兄弟结点对应的空间区域没有重叠,而没有重叠的区域划分可以使空间索引搜索的速度大大
提高,克服了R树中多路查询的问题,但同时它也存在着一些缺陷,如对某个最小约束矩形的划分,可能
会引起相关子树上其他结点也需要重新划分,向下分裂操作可能使得已经划分好了的结点被重新划分,
空间对象在R+树的叶结点中被重复标记,完成删除运算后,必须对R+树进行重建等,同时由于在插入和
删除空间对象时要保证兄弟结点对应的空间区域不重叠,而使插入和删除操作的效率降低。
R*树是最有效的R树变种,它能对覆盖区域、重叠面积和边界周长进行启发式地优化,并通过重新插入节
点重建R.树以提高其性能,但重新插入这个过程相当繁琐,其实现过程太过漫长。压缩R树的空间数据
集是预先己知的,通过预先对数据进行合理有效的组织,可以保证其具有很高的空间利用率和良好的查
询效率,但由于其不能进行动态插入和删除,因而其应用受到了很大限制。
R树是B树在多维空间的扩展,是一种平衡的树结构。R树结构采用平行于数据空间轴的最小的边界矩形来
近似复杂的空间对象,其主要优点是用一定数量的字节来表示一个复杂的对象。尽管这样会丢失很多的
信息,但是空间物体的最小边界矩形保留了物体的最重要的几何特性,即空间物体的位置和其在整个坐
标轴上的范围。
操作编辑
搜索
R树的搜索操作很简单,跟B树上的搜索十分相似。它返回的结果是所有符合查找信息的记录条目。而输
入是什么?就我个人的理解,输入不仅仅是一个范围了,它更可以看成是一个空间中的矩形。也就是说
,我们输入的是一个搜索矩形。
先给出伪代码:
Function:Search
描述:假设T为一棵R树的根结点,查找所有搜索矩形S覆盖的记录条目。
S1:[查找子树]如果T是非叶子结点,如果T所对应的矩形与S有重合,那么检查所有T中存储的条目,对于
所有这些条目,使用Search操作作用在每一个条目所指向的子树的根结点上(即T结点的孩子结点)。
S2:[查找叶子结点]如果T是叶子结点,如果T所对应的矩形与S有重合,那么直接检查S所指向的所有记录
条目。返回符合条件的记录。
插入
R树的插入操作也同B树的插入操作类似。当新的数据记录需要被添加入叶子结点时,若叶子结点溢出,
那么我们需要对叶子结点进行分裂操作。显然,叶子结点的插入操作会比搜索操作要复杂。插入操作需
要一些辅助方法才能够完成。
来看一下伪代码:
Function:Insert
描述:将新的记录条目E插入给定的R树中。
I1:[为新记录找到合适插入的叶子结点]开始ChooseLeaf方法选择叶子结点L以放置记录E。
I2:[添加新记录至叶子结点]如果L有足够的空间来放置新的记录条目,则向L中添加E。如果没有足够的
空间,则进行SplitNode方法以获得两个结点L与LL,这两个结点包含了所有原来叶子结点L中的条目与新
条目E。
I3:[将变换向上传递]开始对结点L进行AdjustTree操作,如果进行了分裂操作,那么同时需要对LL进行
AdjustTree操作。
I4:[对树进行增高操作]如果结点分裂,且该分裂向上传播导致了根结点的分裂,那么需要创建一个新
的根结点,并且让它的两个孩子结点分别为原来那个根结点分裂后的两个结点。
Function:ChooseLeaf
描述:选择叶子结点以放置新条目E。
CL1:[Initialize]设置N为根结点。
CL2:[叶子结点的检查]如果N为叶子结点,则直接返回N。
CL3:[选择子树]如果N不是叶子结点,则遍历N中的结点,找出添加E.I时扩张最小的结点,并把该结点
定义为F。如果有多个这样的结点,那么选择面积最小的结点。
CL4:[下降至叶子结点]将N设为F,从CL2开始重复操作。
Function:AdjustTree
描述:叶子结点的改变向上传递至根结点以改变各个矩阵。在传递变换的过程中可能会产生结点的分裂
。
AT1:[初始化]将N设为L。
AT2:[检验是否完成]如果N为根结点,则停止操作。
AT3:[调整父结点条目的最小边界矩形]设P为N的父节点,EN为指向在父节点P中指向N的条目。调整EN.I
以保证所有在N中的矩形都被恰好包围。
AT4:[向上传递结点分裂]如果N有一个刚刚被分裂产生的结点NN,则创建一个指向NN的条目ENN。如果P
有空间来存放ENN,则将ENN添加到P中。如果没有,则对P进行SplitNode操作以得到P和PP。
AT5:[升高至下一级]如果N等于L且发生了分裂,则把NN置为PP。从AT2开始重复操作。
删除
R树的删除操作与B树的删除操作会有所不同,不过同B树一样,会涉及到压缩等操作。相信读者看完以下
的伪代码之后会有所体会。R树的删除同样是比较复杂的,需要用到一些辅助函数来完成整个操作。
伪代码如下:
Function:Delete
描述:将一条记录E从指定的R树中删除。
D1:[找到含有记录的叶子结点]使用FindLeaf方法找到包含有记录E的叶子结点L。如果搜索失败,则直
接终止。
D2:[删除记录]将E从L中删除。
D3:[传递记录]对L使用CondenseTree操作
D4:[缩减树]当经过以上调整后,如果根结点只包含有一个孩子结点,则将这个唯一的孩子结点设为根
结点。
Function:FindLeaf
描述:根结点为T,期望找到包含有记录E的叶子结点。
FL1:[搜索子树]如果T不是叶子结点,则检查每一条T中的条目F,找出与E所对应的矩形相重合的F(不
必完全覆盖)。对于所有满足条件的F,对其指向的孩子结点进行FindLeaf操作,直到寻找到E或者所有
条目均以被检查过。
FL2:[搜索叶子结点以找到记录]如果T是叶子结点,那么检查每一个条目是否有E存在,如果有则返回T
。
Function:CondenseTree
描述:L为包含有被删除条目的叶子结点。如果L的条目数过少(小于要求的最小值m),则必须将该叶子
结点L从树中删除。经过这一删除操作,L中的剩余条目必须重新插入树中。此操作将一直重复直至到达
根结点。同样,调整在此修改树的过程所经过的路径上的所有结点对应的矩形大小。
CT1:[初始化]令N为L。初始化一个用于存储被删除结点包含的条目的链表Q。
CT2:[找到父条目]如果N为根结点,那么直接跳转至CT6。否则令P为N的父结点,令EN为P结点中存储的
指向N的条目。
CT3:[删除下溢结点]如果N含有条目数少于m,则从P中删除EN,并把结点N中的条目添加入链表Q中。
CT4:[调整覆盖矩形]如果N没有被删除,则调整EN.I使得其对应矩形能够恰好覆盖N中的所有条目所对应
的矩形。
CT5:[向上一层结点进行操作]令N等于P,从CT2开始重复操作。
CT6:[重新插入孤立的条目]所有在Q中的结点中的条目需要被重新插入。原来属于叶子结点的条目可以
使用Insert操作进行重新插入,而那些属于非叶子结点的条目必须插入删除之前所在层的结点,以确保
它们所指向的子树还处于相同的层。
========
红黑树
之前看了很多写红黑树的博客,但是感觉都讲的不太清楚!没说这样操作如何使他保持平衡的,于是疑
惑重重,就看不下去了,一次不经意看到一个人说维基百科的红黑树讲的好,我就随便点了一下一看—
—这下疯了~,怎么讲的这么好!可以说是把一个复杂的问题,讲得简单化!这太幸福了! 于是我就慢
慢学会了!强烈推荐维基的这个讲解,再也找不到比这还好的讲解了!
下面将是我对红黑树的总结,里面的性感的图片都是维基百科红黑树上的^_^!我讨论的红黑树需建立在
会平衡二叉树的基础上去学,即若不懂“旋转”操作,请看平衡二叉树的旋转操作。
红黑树(RBT)的定义:它或者是一颗空树,或者是具有一下性质的二叉查找树:
1.节点非红即黑。
2.根节点是黑色。
3.所有NULL结点称为叶子节点,且认为颜色为黑。
4.所有红节点的子节点都为黑色。
5.从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点。
看完红黑树的定义是不是可晕?怎么这么多要求!!这怎么约束啊?我刚看到这5条约束,直接无语了,
1-3、4还好说,第5点是怎么回事啊?怎么约束?整这么复杂的条件好干啥啊?我来简单说说呵:第3条
,显然这里的叶子节点不是平常我们所说的叶子节点,如图标有NIL的为叶子节点,为什么不按常规出牌
,因为按一般的叶子节点也行,但会使算法更复杂;第4条,即该树上决不允许存在两个连续的红节点;
第5条,比如图中红8到1左边的叶子节点的路径包含2个黑节点,到6下的叶子节点的路径也包含2个黑节
点。所有性质1-5合起来约束了该树的平衡性能--即该树上的最长路径不可能会大于2倍最短路径。为什
么?因为第1条该树上的节点非红即黑,由于第4条该树上不允许存在两个连续的红节点,那么对于从一
个节点到其叶子节点的一条最长的路径一定是红黑交错的,那么最短路径一定是纯黑色的节点;而又第5
条从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点,这么来说最长路径上的黑节点的数
目和最短路径上的黑节点的数目相等!而又第2条根结点为黑、第3条叶子节点是黑,那么可知:最长路
径<=2*最短路径。一颗二叉树的平衡性能越好,那么它的效率越高!显然红黑树的平衡性能比AVL的略差
些,但是经过大量试验证明,实际上红黑树的效率还是很不错了,仍能达到O(logN),这个我不知道,我
现在不可能做过大量试验,只是听人家这样说,O(∩_∩)O哈哈~但你至少知道他的时间复杂度一定小于
2O(logN)!
上边的性质看个10遍,看懂看透彻再看操作!
插入操作
由于性质的约束:插入点不能为黑节点,应插入红节点。因为你插入黑节点将破坏性质5,所以每次插入
的点都是红结点,但是若他的父节点也为红,那岂不是破坏了性质4?对啊,所以要做一些“旋转”和一
些节点的变色!另为叙述方便我们给要插入的节点标为N(红色),父节点为P,祖父节点为G,叔节点为
U。下边将一一列出所有插入时遇到的情况:
情形1:该树为空树,直接插入根结点的位置,违反性质1,把节点颜色有红改为黑即可。
情形2:插入节点N的父节点P为黑色,不违反任何性质,无需做任何修改。
情形1很简单,情形2中P为黑色,一切安然无事,但P为红就不一样了,下边是P为红的各种情况,也是
真正要学的地方!
情形3:N为红,P为红,(祖节点一定存在,且为黑,下边同理)U也为红,这里不论P是G的左孩子,还
是右孩子;不论N是P的左孩子,还是右孩子。
操作:如图把P、U改为黑色,G改为红色,未结束。
解析:N、P都为红,违反性质4;若把P改为黑,符合性质4,显然左边少了一个黑节点,违反性质5;所
以我们把G,U都改为相反色,这样一来通过G的路径的黑节点数目没变,即符合4、5,但是G变红了,若G
的父节点又是红的不就有违反了4,是这样,所以经过上边操作后未结束,需把G作为起始点,即把G看做
一个插入的红节点继续向上检索----属于哪种情况,按那种情况操作~要么中间就结束,要么知道根结点
(此时根结点变红,一根结点向上检索,那木有了,那就把他变为黑色吧)。
情形4:N为红,P为红,U为黑,P为G的左孩子,N为P的左孩子(或者P为G的右孩子,N为P的左孩子;反
正就是同向的)。
操作:如图P、G变色,P、G变换即左左单旋(或者右右单旋),结束。
解析:要知道经过P、G变换(旋转),变换后P的位置就是当年G的位置,所以红P变为黑,而黑G变为红
都是为了不违反性质5,而维持到达叶节点所包含的黑节点的数目不变!还可以理解为:也就是相当于(
只是相当于,并不是实事,只是为了更好理解;)把红N头上的红节点移到对面黑U的头上;这样即符合
了性质4也不违反性质5,这样就结束了。
情形5:N为红,P为红,U为黑,P为G的左孩子,N为P的右孩子(或者P为G的右孩子,N为P的左孩子;反
正两方向相反)。
操作:需要进行两次变换(旋转),图中只显示了一次变换-----首先P、N变换,颜色不变;然后就变成
了情形4的情况,按照情况4操作,即结束。
解析:由于P、N都为红,经变换,不违反性质5;然后就变成4的情形,此时G与G现在的左孩子变色,并
变换,结束。
删除操作
我们知道删除需先找到“替代点”来替代删除点而被删除,也就是删除的是替代点,而替代点N的至少有
一个子节点为NULL,那么,若N为红色,则两个子节点一定都为NULL(必须地),那么直接把N删了,不
违反任何性质,ok,结束了;若N为黑色,另一个节点M不为NULL,则另一个节点M一定是红色的,且M的
子节点都为NULL(按性质来的,不明白,自己分析一下)那么把N删掉,M占到N的位置,并改为黑色,不
违反任何性质,ok,结束了;若N为黑色,另一个节点也为NULL,则把N删掉,该位置置为NULL,显然这
个黑节点被删除了,破坏了性质5,那么要以N节点为起始点检索看看属于那种情况,并作相应的操作,
另还需说明N为黑点(也许是NULL,也许不是,都一样),P为父节点,S为兄弟节点(这个我真想给兄弟
节点叫B(brother)多好啊,不过人家图就是S我也不能改,在重画图,太浪费时间了!S也行呵呵,就
当是sister也行,哈哈)分为以下5中情况:
情形1:S为红色(那么父节点P一定是黑,子节点一定是黑),N是P的左孩子(或者N是P的右孩子)。
操作:P、S变色,并交换----相当于AVL中的右右中旋转即以P为中心S向左旋(或者是AVL中的左左中的
旋转),未结束。
解析:我们知道P的左边少了一个黑节点,这样操作相当于在N头上又加了一个红节点----不违反任何性
质,但是到通过N的路径仍少了一个黑节点,需要再把对N进行一次检索,并作相应的操作才可以平衡(
暂且不管往下看)。
情形2:P、S及S的孩子们都为黑。
操作:S改为红色,未结束。
解析:S变为红色后经过S节点的路径的黑节点数目也减少了1,那个从P出发到其叶子节点到所有路径所
包含的黑节点数目(记为num)相等了。但是这个num比之前少了1,因为左右子树中的黑节点数目都减少
了!一般地,P是他父节点G的一个孩子,那么由G到其叶子节点的黑节点数目就不相等了,所以说没有结
束,需把P当做新的起始点开始向上检索。
情形3:P为红(S一定为黑),S的孩子们都为黑。
操作:P该为黑,S改为红,结束。
解析:这种情况最简单了,既然N这边少了一个黑节点,那么S这边就拿出了一个黑节点来共享一下,这
样一来,S这边没少一个黑节点,而N这边便多了一个黑节点,这样就恢复了平衡,多么美好的事情哈!
情形4:P任意色,S为黑,N是P的左孩子,S的右孩子SR为红,S的左孩子任意(或者是N是P的右孩子,S
的左孩子为红,S的右孩子任意)。
操作:SR(SL)改为黑,P改为黑,S改为P的颜色,P、S变换--这里相对应于AVL中的右右中的旋转(或
者是AVL中的左左旋转),结束。
解析:P、S旋转有变色,等于给N这边加了一个黑节点,P位置(是位置而不是P)的颜色不变,S这边少
了一个黑节点;SR有红变黑,S这边又增加了一个黑节点;这样一来又恢复了平衡,结束。
情形5:P任意色,S为黑,N是P的左孩子,S的左孩子SL为红,S的右孩子SR为黑(或者N是P的有孩子,S
的右孩子为红,S的左孩子为黑)。
操作:SL(或SR)改为黑,S改为红,SL(SR)、S变换;此时就回到了情形4,SL(SR)变成了黑S,S变
成了红SR(SL),做情形4的操作即可,这两次变换,其实就是对应AVL的右左的两次旋转(或者是AVL的
左右的两次旋转)。
解析:这种情况如果你按情形4的操作的话,由于SR本来就是黑色,你无法弥补由于P、S的变换(旋转)
给S这边造成的损失!所以我没先对S、SL进行变换之后就变为情形4的情况了,何乐而不为呢?
好了,这五种情况都讨论完了,我想强调的是:注意哪些分方向的情况,每个分方向的情形就两种情况
,不要搞迷了!下边我写的代码,不用关心是什么方向,我主要是用一个指针数组即child[2],0代表左
,1代表右,进行两个节点的变换(旋转)的时候只需向conversion(&T,direction);传入父节点指针的
地址及子节点在父节点的方位(0或1);有兴趣可以看代码.
欢迎大家留言指正哦^_^
下边贴上我的C代码:
简介:主要是用递归实现插入、删除,回溯时检索并恢复平衡。
复制代码
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #define RED 0
5 #define BACK 1
6
7 typedef int Elemtype;
8
9 //定义一个红黑树的结点
10 typedef struct Red_Back_Tree
11 {
12 Elemtype e;
13 int color;
14 struct Red_Back_Tree * child[2];
15 }* RBT;
16
17 // 两个节点变换函数
18 void conversion(RBT *T,int direction);
19
20 // 删除一个节点的所用函数
21 int DeleteRBT(RBT *T,Elemtype e); // 删除主(接口)函数
22 int find_replace_point(RBT gogal,RBT *l); // 寻找替代点
23 int keep_balance_for_delete(RBT *T,int direction); // 删除的平衡操作
24 int do_with_start_point(RBT gogal,RBT *T,int direction); // 处理
第一个起始点
25
26 // 插入一个节点的所用函数
27 int InsertRBT(RBT *T,Elemtype e); // 插入接口函数
28 int _InsertRBT(RBT *T,Elemtype e); // 插入主函数
29 int keep_balance_for_insert(RBT *T,int firdirection,Elemtype e);// 插入的平衡操作
30 RBT create_one_node(Elemtype e); // 新建一个节点
31
32
33
34 void conversion(RBT *T,int direction)
35 {
36 RBT f=(*T),s=f->child[direction],ss=s->child[!direction];
37
38 f->child[direction]=ss;
39 s->child[!direction]=f;
40 *T=s;
41 }
42
43 //★★★★★★★★★★★★★★★★★删除操作★★★★★★★★★★★★★★★★★★★★★
★★★★★★
44
45 int do_with_start_point(RBT gogal,RBT *T,int direction)
46 {
47 gogal->e=(*T)->e;
48 if(BACK==((*T)->color))
49 {
50 if(NULL!=(*T)->child[direction])
51 {
52 (*T)->e=(*T)->child[direction]->e;
53 free((*T)->child[direction]);
54 (*T)->child[direction]=NULL;
55 return 1;
56 }
57 else
58 {
59 free((*T));
60 *T=NULL;
61 return 0;
62 }
63 }
64 else
65 {
66 free((*T));
67 (*T)=NULL;
68 return 1;
69 }
70 }
71
72 int keep_balance_for_delete(RBT *T,int direction)
73 {
74 RBT p=(*T),b=p->child[!direction];
75
76 if(RED==b->color)
77 {
78 p->color=RED;
79 b->color=BACK;
80 // conversion(&p,!direction);//很恐怖的一个写法,偶然中发现:这里传的地址是假的!
不是T!!
81 // 考我怎么这么傻逼!!如果不是及时发现,到调试时将是
无限恐怖
82 // 将是一个巨大的隐藏的BUG!!!将会带来巨大的麻烦!!!
83 conversion(T,!direction);
84 return keep_balance_for_delete(&((*T)->child[direction]),direction);
85 }
86 else if(BACK==p->color && BACK==b->color &&
87 (NULL==b->child[0] || BACK==b->child[0]->color) &&
88 (NULL==b->child[1] || BACK==b->child[1]->color)) //这里感觉不美,就一次为
NULL却每次要
89 { //判断是否为NULL,不美……
90 b->color=RED;
91 return 0;
92 }
93 else if(RED==p->color &&
94 (NULL==b->child[0] || BACK==b->child[0]->color) &&
95 (NULL==b->child[1] || BACK==b->child[1]->color))
96 {
97 p->color=BACK;
98 b->color=RED;
99 return 1;
100 }
101 // 第一次调试
102 // 调试原因:由于删除0点未按预料的操作应该是情况④,却按⑤操作
103 // 错误的地方:RED==b->child[!direction] ! 丢了->color 这个错误我上边错了几次,不过
编译器报错改了过来
104 // 这次的编译器不报错,看代码也看不错来,最后追究到这里,一一对照才发现!!!
105 // else if(BACK==b->color && (NULL!=b->child[!direction] && RED==b->child[!
direction]))
106 else if(BACK==b->color && (NULL!=b->child[!direction] && RED==b->child[!
direction]->color))
107 {
108 b->color=p->color;
109 p->color=BACK;
110 b->child[!direction]->color=BACK;
111 conversion(T,!direction);
112 return 1;
113 }
114 else
115 {
116 b->child[direction]->color=p->color;
117 p->color=BACK;
118 conversion(&(p->child[!direction]),direction);//这里的p写的才算不错!即p也(*T)都
行,一样!
119 conversion(T,!direction);
120 return 1;
121 }
122
123 }
124
125 int find_replace_point(RBT gogal,RBT *l)
126 {
127 if(NULL!=(*l)->child[0])
128 {
129 if(find_replace_point(gogal,&(*l)->child[0])) return 1;
130 return keep_balance_for_delete(l,0);
131 //...
132 }
133 // 第二次调试---其实没F5,F10,F11,根据结果猜测,到这里看看还真是的!
134 // 调试原因:删除0好了,删除1又错了---2不见了,1还在
135 // 错误的地方:就在这里,找到替代点,却没有“替代”,这等于把替代点删了...
136 // 这里很明显,gogal这个删除点指针根本就没用...我当时忘了吧!!修改如下!
137 // else //替代点为起始点
138 // {
139 // return do_with_start_point(l,1);
140 // }
141 else
142 {
143 return do_with_start_point(gogal,l,1);
144 }
145 }
146
147 int DeleteRBT(RBT *T,Elemtype e)
148 {
149 if(!(*T)) return -1;
150 else if(e>(*T)->e)
151 {
152 if(DeleteRBT(&((*T)->child[1]),e)) return 1;
153 return keep_balance_for_delete(T,1);
154 //...
155 }
156 else if(e<(*T)->e)
157 {
158 if(DeleteRBT(&((*T)->child[0]),e)) return 1;
159 return keep_balance_for_delete(T,0);
160 //...
161 }
162 else
163 {
164 if(NULL!=(*T)->child[1]) //真正的删除点不是起始点,需找替代点
165 {
166 if(find_replace_point((*T),&((*T)->child[1]))) return 1;
167 return keep_balance_for_delete(T,1);
168 //...
169 }
170 else //真正的删除点就是起始点
171 {
172 return do_with_start_point((*T),T,0);
173 }
174 }
175 }
176 //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
★★★★★★
177
178
179 //★★★★★★★★★★★★★★★★★★★插入操作★★★★★★★★★★★★★★★★★★★
★★★★★★
180
181 RBT create_one_node(Elemtype e)
182 {
183 RBT p=(RBT)malloc(sizeof(struct Red_Back_Tree));
184 p->e=e; p->color=RED;
185 p->child[0]=p->child[1]=NULL;
186 return p;
187 }
188
189 int keep_balance_for_insert(RBT *T,int firdirection,Elemtype e)
190 {
191 RBT p=(*T)->child[firdirection],u=(*T)->child[!firdirection];
192 int secdirection=( (e>p->e) ? 1 : 0 ); // 查处第二个方向
193
194 if(NULL!=u && RED==u->color) /*****③叔节点为红色*****/
195 {
196 p->color=BACK;
197 u->color=BACK;
198 (*T)->color=RED;
199 return 1; //继续...
200 }
201 else /*****④叔节点为黑色*****/
202 {
203 if(firdirection!=secdirection) conversion(&((*T)->child
[firdirection]),secdirection);
204 (*T)->color=RED; (*T)->child[firdirection]->color=BACK;
205 conversion(T,firdirection);
206 return 0;
207 }
208 }
209
210 int _InsertRBT(RBT *T,Elemtype e)
211 {
212 int info=0;
213 if(NULL==(*T)) /*****①插入到根节点*****/ //这里只是包含
这种情况
214 {
215 *T=create_one_node(e);
216 (*T)->color=RED;
217 info=1;
218 }
219 else if(e>((*T)->e))
220 {
221 info=_InsertRBT(&(*T)->child[1],e);
222 if(info<1) return info;
223 else if(info==1) /*****②父节点为黑******/
224 {
225 if(BACK==((*T)->color)) info--;
226 else info++;
227 }
228 else
229 {
230 info=keep_balance_for_insert(T,1,e);
231 }
232
233 }
234 else if(e<((*T)->e))
235 {
236 info=_InsertRBT(&((*T)->child[0]),e);
237 if(info<1) return info;
238 else if(info==1)
239 {
240 if(BACK==((*T)->color)) info--;
241 else info++;
242 }
243 else
244 {
245 info=keep_balance_for_insert(T,0,e);
246 }
247
248 }
249 else return info=-1;
250
251 return info;
252 }
253
254 int InsertRBT(RBT *T,Elemtype e) //插入节点函数返回值: -1->改点已存在 0->成功插入
255 {
256 int info=0; // info: -1->已存在 0->结束 1->回溯到父节点 2->回溯到祖节点
257
258 //2011年11月30日9:13:47 昨天晚上最后又想来这里这个if可以不要即可,也就是把它也放到
_InsertRBT
259 //内处理,在InsertRBT中有个判断即可!即改成下边的写法!
260 // if(NULL==(*T)) /*****①插入到根节点*****/
261 // {
262 // *T=create_one_node(e);
263 // (*T)->color=BACK;
264 // }
265 // else
266 // {
267 // info=_InsertRBT(T,e); // 经过再三思考,这里info的返回值只可能为:-1 0
1
268 // if(info>0) (*T)->color=BACK,info=0; // 查看根节点是否为红
269 // }
270
271 info=_InsertRBT(T,e);
272 if(info==1) (*T)->color=BACK,info=0;
273 // 为了防止根结点变为红,它其实是处理了两种情况的后遗症
274 // 分别是:③情况回溯上来,根节点变红 ①情况插入点即为根节点,为红
275 // 这里没有直接把根结点变黑,主要是为了与_InsertRBT保持一致的写法,其实都行!
276 return info;
277 }
278 //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
★★★★★★
279
280
281 //******************JUST FOR TEST********************//
282 RBT queue[1000];
283 void print(RBT cur)
284 {
285 int front=0,rear=0;
286 int count=1,temp=0;
287
288 if(NULL==cur)
289 {
290 printf("NULL\n");
291 return ;
292 }
293
294 queue[rear]=cur;
295 while(front<=rear)
296 {
297 cur=queue[front++]; count--;
298 if(NULL!=cur->child[0]) queue[++rear]=cur->child[0],temp++;
299 if(NULL!=cur->child[1]) queue[++rear]=cur->child[1],temp++;
300
301 printf("%d color->",cur->e);
302 if(BACK==cur->color) printf("BACK |");
303 else printf("RED |");
304
305 if(0==count)
306 {
307 count=temp;
308 temp=0;
309 printf("\n");
310 }
311 }
312 }
313 //*****************************************************//
314
315 //*****************DEAR MAIN***************************//
316 int main()
317 {
318 RBT T=NULL;
319 int i,nodenum=100;
320
321 print(T);
322 printf("\n");
323
324 printf("\n插入操作\n");
325 for(i=0;i<nodenum;i++)
326 {
327 InsertRBT(&T,i);
328 printf("插入%d\n",i);
329 print(T);
330 printf("\n");
331 }
332
333 // print(T);
334 printf("\n删除操作:\n");
335
336 for(i=0;i<nodenum;i++)
337 {
338 DeleteRBT(&T,i);
339 printf("删除%d\n",i);
340 print(T);
341 printf("\n");
342 }
343
344 return 0;
345 }
========