树学习总结

74 篇文章 0 订阅

树状图是一种数据结构,它是由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 }
========
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值