多路查找树

 前面讨论的查找都是内查询算法,被查询的数据都在内存。当查询的数据放在外存,用平衡二叉树作磁盘文件的索引组织时,若以结点为内外存交换的单位,则找到需要的关键字之前,平均要进行lgn次磁盘读操作,而磁盘、光盘的读写时间要比随机存取的内存代价大得多。其二,外存的存取是以“页”为单位的,一页的大小通常是1024字节或2048字节。

 针对上述特点,1972R.BayerE.M.Cright提出了一种B-树的多路平衡查找树,以适合磁盘等直接存取设备上组织动态查找表B-树上算法的执行时间主要由读、写磁盘的次数来决定,故一次I/O操作应读写尽可能多的信息。因此B-树的结点规模一般以一个磁盘页为单位。一个结点包含的关键字及其孩子个数取决于磁盘页的大小。

一、基本概念

B-树又称为多路平衡查找树。

         一棵度为mB-树称为mB_树。一个结点有k个孩子时,必有k-1个关键字才能将子树中所有关键字划分为k个子集。B-树中所有结点的孩子结点最大值称为B-树的阶,通常用m表示。从查找效率考虑,一般要求m3。一棵m阶的B-树或者是一棵空树,或者是满足下列要求的m叉树:

1)根结点或者为叶子,或者至少有两棵子树,至多有m棵子树。

2)除根结点外,所有非终端结点至少有ceil(m/2)棵子树,至多有m棵子树。

3)所有叶子结点都在树的同一层上。

4)每个结点的结构为:

       nA0K1A1K2A2,…  KnAn

其中,Ki(1in)为关键字,且Ki<Ki+1(1in-1)

        Ai(0in)为指向子树根结点的指针。且Ai所指子树所有结点中的关键字均小于Ki+1An所指子树中所有结点的关键字均大于Kn

n为结点中关键字的个数,满足ceil(m/2)-1nm-1

        比如,一棵3B-树,m=3。它满足: 

1)每个结点的孩子个数小于等于3 

2)除根结点外,其他结点至少有=2个孩子。 

3根结点有两个孩子结点。 

4)除根结点外的所有结点的n大于等于=1,小于等于2 

5)所有叶结点都在同一层上。
   29、B-树及其查找 - 墨涵 - 墨涵天地

二、B-树查找的算法思想

1B-树的查找

B-树的查找过程:根据给定值查找结点和在结点的关键字中进行查找交叉进行。首先从根结点开始重复如下过程:

       若 比结点的第一个关键字小,则查找在该结点第一个指针指向的结点进行;若等于结点中某个关键字,则查找成功;若在两个关键字之间,则查找在它们之间的指针指 向的结点进行;若比该结点所有关键字大,则查找在该结点最后一个指针指向的结点进行;若查找已经到达某个叶结点,则说明给定值对应的数据记录不存在,查找 失败。

2.  B-树的插入

插入的过程分两步完成:

   1)利用前述的B-树的查找算法查找关键字的插入位置。若找到,则说明该关键字已经存在,直接返回。否则查找操作必失败于某个最低层的非终端结点上。

   2)判断该结点是否还有空位置。即判断该结点的关键字总数是否满足n<=m-1。若满足,则说明该结点还有空位置,直接把关键字k插入到该结点的合适位置上。若不满足,说明该结点己没有空位置,需要把结点分裂成两个。

分裂的方法是:生成一新结点。把原结点上的关键字和k按升序排序后,从中间位置把关键字(不包括中间位置的关键字)分成两部分。左部分所含关键字放在旧结点中,右部分所含关键字放在新结点中,中间位置的关键字连同新结点的存储位置插入到父结点中。如果父结点的关键字个数也超过(m-1),则要再分裂,再往上插。直至这个过程传到根结点为止。

29、B-树及其查找 - 墨涵 - 墨涵天地
29、B-树的插入、查找、删除 - EdwardLewis - 墨涵天地

29、B-树及其查找 - 墨涵 - 墨涵天地

29、B-树及其查找 - 墨涵 - 墨涵天地

3B-树的删除

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。对任意关键字的删除都可以转化为对最下层关键字的删除。

29、B-树及其查找 - 墨涵 - 墨涵天地

如图示:

a被删关键字Ki所在结点的关键字数目不小于ceil(m/2),则只需从结点中删除Ki和相应指针Ai,树的其它部分不变。

29、B-树及其查找 - 墨涵 - 墨涵天地

b、被删关键字Ki所在结点的关键字数目等于ceil(m/2)-1,则需调整。调整过程如上面所述。

29、B-树及其查找 - 墨涵 - 墨涵天地

c、被删关键字Ki所在结点和其相邻兄弟结点中的的关键字数目均等于ceil(m/2)-1,假设该结点有右兄弟,且其右兄弟结点地址由其双亲结点指针Ai所指。则在删除关键字之后,它所在结点的剩余关键字和指针,加上双亲结点中的关键字Ki一起,合并到Ai所指兄弟结点中(若无右兄弟,则合并到左兄弟结点中)。如果因此使双亲结点中的关键字数目少于ceil(m/2)-1,则依次类推。

29、B-树及其查找 - 墨涵 - 墨涵天地

29、B-树及其查找 - 墨涵 - 墨涵天地

三、B-树的C语言描述

1、存储结构

29、B-树及其查找 - 墨涵 - 墨涵天地

29、B-树及其查找 - 墨涵 - 墨涵天地

2、插入

29、B-树及其查找 - 墨涵 - 墨涵天地

3、查找

29、B-树及其查找 - 墨涵 - 墨涵天地

四、B-树的C语言实现

  1 #include "stdio.h"
  2 #include "stdlib.h"
  3 #include "math.h"
  4 #define OK 1
  5 #define ERROR -1
  6 #define m 3 //3阶树
  7 #define N 16 //数据元素个数
  8 #define MAX 5 //字符串最大长度+1
  9 typedef int KeyType;
 10 struct Others  //记录的其它部分
 11 {
 12 char info[MAX];
 13 };
 14 struct Record
 15 {
 16 KeyType key; //关键字
 17 Others others; //其它部分
 18 };
 19 typedef struct BTNode
 20 {
 21 int keynum; //结点中关键字个数
 22 BTNode *parent;//指向双亲节点
 23    struct Node  //结点向量类型
 24    {
 25    KeyType key; //关键字向量
 26    BTNode *ptr;//子树指针向量
 27    Record *recptr; //记录向量指针
 28    }node[m+1]; //key,recptr的0号单元未用
 29 }BTNode,*BTree;
 30 struct Result //B树的查找结果类型
 31 {
 32 BTNode *pt; //指向找到的结点
 33 int i; //在节点中关键字序号,1...m
 34 int tag; //1表示查找成功,0表示查找失败。
 35 };
 36 
 37 int InitDSTable(BTree &DT)
 38 {
 39 DT=NULL;
 40 return OK;
 41 }//InitDSTable
 42 
 43 void DestroyDSTable(BTree &DT)
 44 {
 45 int i;
 46 if(DT) //非空树
 47     {
 48      for(i=0;i<=DT->keynum;i++)
 49          DestroyDSTable(DT->node[i].ptr);
 50      free(DT);
 51      DT=NULL;
 52     }//if
 53 }//DestroyDSTable
 54 
 55 int Search(BTree p,KeyType K)
 56 {//在p->node[1...keytype].key中查找i,使得p->node[i].key<=K<
 57     //p->node[i+1].key
 58     int i=0,j;
 59     for(j=1;j<=p->keynum;j++)
 60         if(p->node[j].key<=K)
 61             i=j;
 62     return i;
 63 }//Search
 64 
 65 void Insert(BTree &q,int i,Record *r,BTree ap)
 66 {//将r->key、r和ap分别插入到q->key[i+1]、
 67     //q->recptr[              i+1]和q->ptr[i+1]中
 68     int j;
 69     for(j=q->keynum;j>i;j--) //空出q->node[i+1]
 70      q->node[j+1]=q->node[j];
 71     q->node[i+1].key=r->key;
 72     q->node[i+1].ptr=ap; //前加入的结点,还没有儿子结点
 73     q->node[i+1].recptr=r;
 74     q->keynum++;
 75 }//Insert
 76 
 77 void NewRoot(BTree &T,Record *r,BTree ap)
 78 {// 生成含信息(T,r,ap)的新的根结点*T,原T和ap为子树指针
 79 BTree p;
 80 p=(BTree)malloc(sizeof(BTNode));
 81 p->node[0].ptr=T;
 82 T=p;
 83 if(T->node[0].ptr)
 84     T->node[0].ptr->parent=T;
 85 T->parent=NULL;
 86 T->keynum=1;
 87 T->node[1].key=r->key;
 88 T->node[1].recptr=r;
 89 T->node[1].ptr=ap;
 90 if(T->node[1].ptr)
 91     T->node[1].ptr->parent=T;
 92 }//NewRoot
 93 
 94 void split(BTree &q,BTree &ap)
 95 {// 将结点q分裂成两个结点,前一半保留,后一半移入新生结点ap
 96 int i,s=(m+1)/2;
 97 ap=(BTree)malloc(sizeof(BTNode));//生成新结点ap
 98 ap->node[0].ptr=q->node[s].ptr;//原来结点中间位置关键字相应指针指向的子树放到
 99                                //新生成结点的0棵子树中去
100 for(i=s+1;i<=m;i++) //后一半移入ap
101    {
102    ap->node[i-s]=q->node[i];
103    if(ap->node[i-s].ptr)
104        ap->node[i-s].ptr->parent=ap;
105    }//for
106    ap->keynum=m-s;
107    ap->parent=q->parent;
108    q->keynum=s-1; // q的前一半保留,修改keynum
109 }//split
110 
111 void InsertBTree(BTree &T,Record *r,BTree q,int i)
112 {//在m阶B树T上结点*q的key[i]与key[i+1]之间插入关键字K的指针r。若引起
113    // 结点过大,则沿双亲链进行必要的结点分裂调整,使T仍是m阶B树。
114 BTree ap=NULL;
115 int finished=false;
116 int s;
117 Record *rx;
118 rx=r;
119 while(q&&!finished)
120    {
121     Insert(q,i,rx,ap);//将r->key、r和ap分别插入到q->key[i+1]、
122                       //q->recptr[i+1]和q->ptr[i+1]中
123     if(q->keynum<m)
124         finished=true;
125     else
126       {//分裂结点*q
127       s=(m+1)/2;
128       rx=q->node[s].recptr;
129       split(q,ap);//将q->key[s+1..m],q->ptr[s..m]和q->recptr[s+1..m]
130                   //移入新结点*ap
131       q=q->parent;
132       if(q)
133           i=Search(q,rx->key);//在双亲结点*q中查找rx->key的插入位置
134       }//else
135    }//while
136 if(!finished) //T是空树(参数q初值为NULL)或根结点已分裂为
137               //结点*q和*ap
138 NewRoot(T,rx,ap);    
139 }//InsertBTree
140 
141 Result SearchBTree(BTree T,KeyType K)
142 {// 在m阶B树T上查找关键字K,返回结果(pt,i,tag)。若查找成功,则特征值
143 // tag=1,指针pt所指结点中第i个关键字等于K;否则特征值tag=0,等于K的
144 // 关键字应插入在指针Pt所指结点中第i和第i+1个关键字之间。
145 BTree p=T,q=NULL; //初始化,p指向待查结点,q指向p的双亲
146 int found=false;
147 int i=0;
148 Result r;
149 while(p&&!found)
150    {
151    i=Search(p,K);//p->node[i].key≤K<p->node[i+1].key
152    if(i>0&&p->node[i].key==K)
153        found=true;
154    else
155      {
156      q=p;
157      p=p->node[i].ptr;//在子树中继续查找
158      }//else
159     }//while
160    r.i=i;
161    if(found)
162      {
163       r.pt=p;
164       r.tag=1;
165      }//if
166    else
167       {
168        r.pt=q;
169        r.tag=0;
170       }//else
171     return r;
172 }//SearchBTree
173 
174 void print(BTNode c,int i) // TraverseDSTable()调用的函数
175  {
176    printf("(%d,%s)",c.node[i].key,c.node[i].recptr->others.info);
177  }//print
178 void TraverseDSTable(BTree DT,void(*Visit)(BTNode,int))
179 {// 初始条件: 动态查找表DT存在,Visit是对结点操作的应用函数
180 // 操作结果: 按关键字的顺序对DT的每个结点调用函数Visit()一次且至多一次
181 int i;
182 if(DT) //非空树
183     {
184       if(DT->node[0].ptr) // 有第0棵子树
185          TraverseDSTable(DT->node[0].ptr,Visit);
186       for(i=1;i<=DT->keynum;i++)
187         {
188          Visit(*DT,i);
189          if(DT->node[i].ptr) // 有第i棵子树
190          TraverseDSTable(DT->node[i].ptr,Visit);
191         }//for
192     }//if
193 }//TraverseDSTable
194 
195 void InputBR(BTree &t,Record r[])
196 {
197 Result s;    
198 for(int i=0;i<N;i++)
199    {
200      s=SearchBTree(t,r[i].key);
201      if(!s.tag)
202        InsertBTree(t,&r[i],s.pt,s.i);
203    }
204 }//InputBR
205 void UserSearch(BTree t)
206 {
207 int i;
208 Result s;
209 printf("\n请输入待查找记录的关键字: ");
210 scanf("%d",&i);
211 s=SearchBTree(t,i);
212 if(s.tag)
213 print(*(s.pt),s.i);
214 else
215 printf("没找到");
216 printf("\n");
217 }//UserSearch
218 void DeleteIt(BTree t,BTNode *dnode,int id)
219 {
220 if(dnode->keynum>=ceil(m/2))
221    {
222     dnode->keynum--;
223     dnode->node[id].ptr=NULL;
224    }//if被删关键字Ki所在结点的关键字数目不小于ceil(m/2),则只需从结点中删除Ki和相应指针Ai,树的其它部分不变。
225 else if((dnode->keynum==(ceil(m/2)-1))&&((id+1)<(m-1))&&dnode->parent->node[id+1].ptr->keynum>(ceil(m/2)-1))
226    {
227     for(int i=1;i<m&&dnode->parent->node[i].key < dnode->parent->node[id+1].ptr->node[1].key;i++)
228         dnode->node[i].key=dnode->parent->node[i].key;
229     dnode->parent->node[1].key=dnode->parent->node[id+1].ptr->node[1].key;
230     (dnode->parent->node[id+1].ptr->keynum)--;
231    }//else if 被删关键字Ki所在结点的关键字数目等于ceil(m/2)-1,则需调整。本次为与右兄弟调整
232 else if((dnode->keynum==(ceil(m/2)-1))&&((id-1)>0    )&&dnode->parent->node[id-1].ptr->keynum>(ceil(m/2)-1))
233    {
234     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++)
235         dnode->node[i].key=dnode->parent->node[i].key;
236     dnode->parent->node[1].key=dnode->parent->node[id-1].ptr->node[dnode->parent->node[id-1].ptr->keynum].key;
237     (dnode->parent->node[id-1].ptr->keynum)--;
238    }//2-else if被删关键字Ki所在结点的关键字数目等于ceil(m/2)-1,则需调整。本次为与左兄弟调整
239 else if((dnode->keynum==(ceil(m/2)-1))&&((id+1)<(m-1))&&dnode->parent->node[id+1].ptr->keynum==(ceil(m/2)-1))
240    {
241     do
242       {
243         BTree tmp;
244         tmp=dnode;
245        dnode->parent->node[id+1].ptr->node[2]=dnode->parent->node[id+1].ptr->node[1];
246        dnode->parent->node[id+1].ptr->node[1]=dnode->parent->node[1];
247        dnode->parent->node[id+1].ptr->keynum++;
248        dnode->parent->node[id+1].ptr->node[0].ptr=dnode->node[1].ptr;
249        dnode->parent->keynum--;
250        dnode->parent->node[id].ptr=NULL;
251        tmp=dnode;
252        if(dnode->parent->keynum>=(ceil(m/2)-1))
253            dnode->parent->node[1]=dnode->parent->node[2];
254        dnode=dnode->parent;
255        free(tmp);
256       }while(dnode->keynum<(ceil(m/2)-1));    //双亲中keynum<
257    }//3-else if被删关键字Ki所在结点和其相邻兄弟结点中的的关键字数目均等于ceil(m/2)-1,本次假设右兄弟存在
258 else if((dnode->keynum==(ceil(m/2)-1))&&(id-1)>0      &&dnode->parent->node[id-1].ptr->keynum==(ceil(m/2)-1))
259    {
260     do
261       {
262         BTree tmp;
263         tmp=dnode;
264        dnode->parent->node[id-1].ptr->node[2]=dnode->parent->node[id-1].ptr->node[1];
265        dnode->parent->node[id-1].ptr->node[1]=dnode->parent->node[1];
266        dnode->parent->node[id-1].ptr->keynum++;
267        dnode->parent->node[id-1].ptr->node[0].ptr=dnode->node[1].ptr;
268        dnode->parent->keynum--;
269        dnode->parent->node[id].ptr=NULL;
270        tmp=dnode;
271        if(dnode->parent->keynum>=(ceil(m/2)-1))
272            dnode->parent->node[1]=dnode->parent->node[2];
273        dnode=dnode->parent;
274        free(tmp);
275       }while(dnode->keynum<(ceil(m/2)-1)); //双亲中keynum<
276    }//4-else if被删关键字Ki所在结点和其相邻兄弟结点中的的关键字数目均等于ceil(m/2)-1,本次假设左兄弟存在
277     else printf("Error!"); //出现异常
278 }//DeleteIt
279 void UserDelete(BTree t)
280 {
281 KeyType date;
282 Result s;
283 printf("Please input the date you want to delete:\n");
284 scanf("%d",&date);
285 s=SearchBTree(t,date);
286 if(!s.tag)  printf("Search failed,no such date\n");
287 else DeleteIt(t,s.pt,s.i);
288 }//UserDelete
289 
290 int main()
291 {
292 Record r[N]={{24,"1"},{45,"2"},{53,"3"},{12,"4"},{37,"5"},
293         {50,"6"},{61,"7"},{90,"8"},{100,"9"},{70,"10"},
294         {3,"11"},{30,"12"},{26,"13"},{85,"14"},{3,"15"},
295         {7,"16"}};    
296 BTree t;
297 InitDSTable(t);
298 InputBR(t,r);
299 printf("按关键字的顺序遍历B_树:\n");
300 TraverseDSTable(t,print);
301 UserSearch(t);
302 UserDelete(t);
303 TraverseDSTable(t,print);
304 DestroyDSTable(t);
305 return 1;
306 }

 

五、复杂度分析

B-树查找包含两种基本动作:

     ●在B-树上查找结点

     ●在结点中找关键字

前一操作在磁盘上进行,后一操作在内存进行。因此查找效率主要由前一操作决定。在磁盘上查找的次数取决于关键字结点在B-树上的层次数。

定理:若n1m3,则对任意一棵具有n个关键字的mB-树,其树高度h至多为logt((n+1)/2)+1t= ceil(m/2)。也就是说根结点到关键字所在结点的路径上涉及的结点数不超过logt((n+1)/2)+1。推理如下:

29、B-树的插入、查找、删除 - 墨涵 - 墨涵天地

29、B-树的插入、查找、删除 - 墨涵 - 墨涵天地

 

29、B-树的插入、查找、删除 - 墨涵 - 墨涵天地

 

转载于:https://www.cnblogs.com/xuyinghui/p/4593250.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值