0%

B-树

B-树的定义

一颗m阶B树,或为空树,或为满足下列特性的m叉树:

  1. 树中每个结点最多含有m棵子树;
  2. 若根结点不是叶子结点,则至少有两颗子树;
  3. 除根之外的所有非终端结点至少有 ⌈m/2⌉ 棵子树 ;
  4. 所有的叶子结点都出现在同一层次上,并且不带信息,通常称为失败结点。失败结点不存在,指向这些结点的指针为空。引入失败结点是为了便于分析B-树的查找性能。
  5. 结点的结构如下:
     (n,P~0~,K~1~,P~1~,K~2~,P~2~,…,K~n~,P~n~)
    其中
    ①Ki(1≤i≤n)为关键字,且关键字按升序排序。
    ②指针Pi(0≤i≤n)指向子树的根结点。Pi-1相当于指向Ki的“左子树”,Pi相当 于指向Ki的“右子树”。
    ③关键字的个数n必须满足:⌈m/2⌉-1≤n≤m-1
  6. 从上述定义可以看出,B-树具有平衡、有序、多路的特点。

B-树的存储结构

1
2
3
4
5
6
typedef struct node{                //B树存储结构 
int keynum; //结点关键字个数
KeyType key[m+1]; //关键字数组,key[0]未使用
struct node *parent; //双亲结点指针
struct node *ptr[m+1]; //孩子结点指针数组
}BTNode,*BTree;

B-树的查找

在B-树上的查找,是一个顺指针查找结点(与二叉树的查找一样),然后在结点查找关键字的过程。

  • 查找结果类型定义:

    1
    2
    3
    4
    5
    typedef struct{                   
    BTNode *pt; //指向找到的结点
    int i; //在结点中的关键字位置;
    int tag; //查找成功与否标志
    }Result;
  • 查找

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    /*
    在树t上查找关键字k,返回结果(pt,i,tag)。
    若查找成功,则特征值tag=1,关键字k是指针pt所指结点中第i个关键字
    否则特征值tag=0,关键字k的插入在pt所指结点的第i个和第i+1个关键字之间
    */
    Result SearchBTree(BTree t,KeyType k){

    BTNode *p=t,*q=NULL; //初始化结点p和结点q,p指向待查结点,q指向p的双亲
    int found=FALSE; //设定查找成功与否标志
    int i=0;
    Result r; //设定返回的查找结果

    while(p&&!found){
    i=SearchBTNode(p,k); //在结点p中查找关键字k,使得p->key[i]<=k<p->key[i+1]
    if(i>0&&p->key[i]==k) //找到待查关键字
    found=OK; //查找成功
    else{ //查找失败
    q=p;
    p=p->ptr[i];
    }
    }

    if(found){ //查找成功
    r.pt=p;
    r.i=i;
    r.tag=1;
    }
    else{ //查找失败
    r.pt=q;
    r.i=i;
    r.tag=0;
    }
    return r; //返回关键字k的位置(或插入位置)
    }

    返回顶部

B-树的插入

插入26时,d结点引起分裂。
插入85时,g结点引起分裂,继而引起e结点分裂。
最后插入7时,先引起c结点分裂,继而引起b结点分裂,又引起a结点分裂。树的深度加一。

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
在树t上结点q的key[i]与key[i+1]之间插入关键字k。
若引起结点过大,则沿双亲链进行必要的结点分裂调整
使t仍是B树
*/
void InsertBTree(BTree &t,int i,KeyType k,BTree p){
BTNode *q;
int finished,newroot_tag,s; //设定需要新结点标志和插入完成标志
KeyType x;
if(p==NULL) //t是空树
NewRoot(t,k,NULL,NULL); //生成仅含关键字k的根结点t
else{
x=k;
q=NULL;
finished=FALSE;
newroot_tag=FALSE;
while(!finished&&!newroot_tag){
InsertBTNode(p,i,x,q); //将关键字x和结点q分别插入到p->key[i+1]和p->ptr[i+1]
if (p->keynum<=Max)
finished=OK; //插入完成
else{
s=(m+1)/2;
SplitBTNode(p,q); //分裂结点
x=p->key[s];
if(p->parent){ //查找x的插入位置
p=p->parent;
i=SearchBTNode(p, x);
}
else //没找到x,需要新结点
newroot_tag=OK;
}
}
if(newroot_tag==OK) //根结点已分裂为结点p和q
NewRoot(t,x,p,q); //生成新根结点t,p和q为子树指针
}
}

返回顶部

B-树的删除

B-树的删除有点复杂,主要就是涉及到删除元素后,要调整树使得还是B-树。关键就是使得:关键字的个数n必须满足:⌈m/2⌉-1≤n≤m-1
第一种,删除12,只需删除关键字和相应指针,树的其它部分不变。
在这里插入图片描述
第二种,删除上图中的50,因为它的有兄弟结点中的关键字个数大于 ⌈m/2⌉-1,可以拿走一个,需要做如下调整:
在这里插入图片描述
第三种,删除上图中的53,因为它的有兄弟结点中的关键字个数不大于 ⌈m/2⌉-1,不能再拿了,只能合并了,需要做如下调整:
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int BTNodeDelete(BTree p,KeyType k){
//在结点p中查找并删除关键字k
int i;
int found; //查找标志
if(p==NULL)
return 0;
else{
found=FindBTNode(p,k,i); //返回查找结果
if(found){ //查找成功
if(p->ptr[i-1]!=NULL){ //删除的是非叶子结点
Substitution(p,i); //寻找相邻关键字(右子树中最小的关键字)
BTNodeDelete(p->ptr[i],p->key[i]); //执行删除操作
}
else
Remove(p,i); //从结点p中位置i处删除关键字
}
else
found=BTNodeDelete(p->ptr[i],k); //沿孩子结点递归查找并删除关键字k
if(p->ptr[i]!=NULL)
if(p->ptr[i]->keynum<Min) //删除后关键字个数小于MIN
AdjustBTree(p,i); //调整B树
return found;
}
}

返回顶部

总的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
#include <stdio.h>
#include <stdlib.h>

#define OK 1
#define FALSE 0
#define MAXM 10 //定义B树的最大的阶数

const int m=3; //设定B树的阶数
const int Max=m-1; //结点的最大关键字数量
const int Min=(m-1)/2; //结点的最小关键字数量

typedef int KeyType; //KeyType为关键字类型

typedef struct node{ //B树存储结构
int keynum; //结点关键字个数
KeyType key[m+1]; //关键字数组,key[0]未使用
struct node *parent; //双亲结点指针
struct node *ptr[m+1]; //孩子结点指针数组
}BTNode,*BTree;

typedef struct{
BTNode *pt; //指向找到的结点
int i; //在结点中的关键字位置;
int tag; //查找成功与否标志
}Result;

int InitBTree(BTree &t){ //初始化B树
t=NULL;
return OK;
}

int SearchBTNode(BTree p,KeyType k){ //在结点p中查找关键字k的插入位置i
int i=0;
for(i=0;i<p->keynum&&p->key[i+1]<=k;i++);
return i;
}

/*
在树t上查找关键字k,返回结果(pt,i,tag)。
若查找成功,则特征值tag=1,关键字k是指针pt所指结点中第i个关键字
否则特征值tag=0,关键字k的插入位置为pt结点的第i个
*/
Result SearchBTree(BTree t,KeyType k){

BTree p=t,q=NULL; //初始化结点p和结点q,p指向待查结点,q指向p的双亲
int found=FALSE; //设定查找成功与否标志
int i=0;
Result r; //设定返回的查找结果

while(p&&!found){
i=SearchBTNode(p,k); //在结点p中查找关键字k,使得p->key[i]<=k<p->key[i+1]
if(i>0&&p->key[i]==k) //找到待查关键字
found=OK; //查找成功
else{ //查找失败
q=p;
p=p->ptr[i];
}
}

if(found){ //查找成功
r.pt=p;
r.i=i;
r.tag=1;
}
else{ //查找失败
r.pt=q;
r.i=i;
r.tag=0;
}
return r; //返回关键字k的位置(或插入位置)
}

void InsertBTNode(BTree &p,int i,KeyType k,BTree q){
//将关键字k和结点q分别插入到p->key[i+1]和p->ptr[i+1]中
int j;
for(j=p->keynum;j>i;j--){ //整体后移空出一个位置
p->key[j+1]=p->key[j];
p->ptr[j+1]=p->ptr[j];
}
p->key[i+1]=k;
p->ptr[i+1]=q;
if(q!=NULL)
q->parent=p;
p->keynum++;
}


void SplitBTNode(BTree &p,BTree &q){
//将结点p分裂成两个结点,前一半保留,后一半移入结点q
int i;
int s=(m+1)/2;
q=(BTNode *)malloc(sizeof(BTNode)); //给结点q分配空间

q->ptr[0]=p->ptr[s]; //后一半移入结点q
for(i=s+1;i<=m;i++){
q->key[i-s]=p->key[i];
q->ptr[i-s]=p->ptr[i];
}
q->keynum=p->keynum-s;
q->parent=p->parent;
for(i=0;i<=p->keynum-s;i++) //修改双亲指针
if(q->ptr[i]!=NULL)
q->ptr[i]->parent=q;
p->keynum=s-1; //结点p的前一半保留,修改结点p的keynum
}


void NewRoot(BTree &t,KeyType k,BTree p,BTree q){
//生成新的根结点t,原p和q为子树指针
t=(BTNode *)malloc(sizeof(BTNode)); //分配空间
t->keynum=1;
t->ptr[0]=p;
t->ptr[1]=q;
t->key[1]=k;
if(p!=NULL) //调整结点p和结点q的双亲指针
p->parent=t;
if(q!=NULL)
q->parent=t;
t->parent=NULL;
}

/*
在树t上结点q的key[i]与key[i+1]之间插入关键字k。
若引起结点过大,则沿双亲链进行必要的结点分裂调整
使t仍是B树
*/
void InsertBTree(BTree &t,int i,KeyType k,BTree p){
BTNode *q;
int finished,newroot_tag,s; //设定需要新结点标志和插入完成标志
KeyType x;
if(p==NULL) //t是空树
NewRoot(t,k,NULL,NULL); //生成仅含关键字k的根结点t
else{
x=k;
q=NULL;
finished=FALSE;
newroot_tag=FALSE;
while(!finished&&!newroot_tag){
InsertBTNode(p,i,x,q); //将关键字x和结点q分别插入到p->key[i+1]和p->ptr[i+1]
if (p->keynum<=Max)
finished=OK; //插入完成
else{
s=(m+1)/2;
SplitBTNode(p,q); //分裂结点
x=p->key[s];
if(p->parent){ //查找x的插入位置
p=p->parent;
i=SearchBTNode(p, x);
}
else //没找到x,需要新结点
newroot_tag=OK;
}
}
if(newroot_tag==OK) //根结点已分裂为结点p和q
NewRoot(t,x,p,q); //生成新根结点t,p和q为子树指针
}
}

void Remove(BTree p,int i){ //从p结点删除key[i]和它的孩子指针ptr[i]

int j;
for(j=i+1;j<=p->keynum;j++){ //前移删除key[i]和ptr[i]
p->key[j-1]=p->key[j];
p->ptr[j-1]=p->ptr[j];
}
p->keynum--;
}


void Substitution(BTree p,int i){
//查找被删关键字p->key[i](在非叶子结点中)的替代叶子结点(右子树中值最小的关键字)
BTNode *q;
for(q=p->ptr[i];q->ptr[0]!=NULL;q=q->ptr[0]);
p->key[i]=q->key[1]; //复制关键字值
}


void MoveRight(BTree p,int i){
/*将双亲结点p中的最后一个关键字移入右结点q中
将左结点aq中的最后一个关键字移入双亲结点p中*/
int j;
BTNode *q=p->ptr[i];
BTNode *aq=p->ptr[i-1];

for(j=q->keynum;j>0;j--){ //将右兄弟q中所有关键字向后移动一位
q->key[j+1]=q->key[j];
q->ptr[j+1]=q->ptr[j];
}

q->ptr[1]=q->ptr[0]; //从双亲结点p移动关键字到右兄弟q中
q->key[1]=p->key[i];
q->keynum++;

p->key[i]=aq->key[aq->keynum]; //将左兄弟aq中最后一个关键字移动到双亲结点p中
p->ptr[i]->ptr[0]=aq->ptr[aq->keynum];
aq->keynum--;
}


void MoveLeft(BTree p,int i){
/*将双亲结点p中的第一个关键字移入左结点aq中,
将右结点q中的第一个关键字移入双亲结点p中*/
int j;
BTNode *aq=p->ptr[i-1];
BTNode *q=p->ptr[i];

aq->keynum++; //把双亲结点p中的关键字移动到左兄弟aq中
aq->key[aq->keynum]=p->key[i];
aq->ptr[aq->keynum]=p->ptr[i]->ptr[0];

p->key[i]=q->key[1]; //把右兄弟q中的关键字移动到双亲节点p中
q->ptr[0]=q->ptr[1];
q->keynum--;

for(j=1;j<=aq->keynum;j++){ //将右兄弟q中所有关键字向前移动一位
aq->key[j]=aq->key[j+1];
aq->ptr[j]=aq->ptr[j+1];
}
}


void Combine(BTree p,int i){
/*将双亲结点p、右结点q合并入左结点aq,
并调整双亲结点p中的剩余关键字的位置*/
int j;
BTNode *q=p->ptr[i];
BTNode *aq=p->ptr[i-1];

aq->keynum++; //将双亲结点的关键字p->key[i]插入到左结点aq
aq->key[aq->keynum]=p->key[i];
aq->ptr[aq->keynum]=q->ptr[0];

for(j=1;j<=q->keynum;j++){ //将右结点q中的所有关键字插入到左结点aq
aq->keynum++;
aq->key[aq->keynum]=q->key[j];
aq->ptr[aq->keynum]=q->ptr[j];
}

for(j=i;j<p->keynum;j++){ //将双亲结点p中的p->key[i]后的所有关键字向前移动一位
p->key[j]=p->key[j+1];
p->ptr[j]=p->ptr[j+1];
}
p->keynum--; //修改双亲结点p的keynum值
free(q); //释放空右结点q的空间
}


void AdjustBTree(BTree p,int i){
//删除结点p中的第i个关键字后,调整B树
if(i==0) //删除的是最左边关键字
if(p->ptr[1]->keynum>Min) //右结点可以借
MoveLeft(p,1);
else //右兄弟不够借
Combine(p,1);
else if(i==p->keynum) //删除的是最右边关键字
if(p->ptr[i-1]->keynum>Min) //左结点可以借
MoveRight(p,i);
else //左结点不够借
Combine(p,i);
else if(p->ptr[i-1]->keynum>Min) //删除关键字在中部且左结点够借
MoveRight(p,i);
else if(p->ptr[i+1]->keynum>Min) //删除关键字在中部且右结点够借
MoveLeft(p,i+1);
else //删除关键字在中部且左右结点都不够借
Combine(p,i);
}


int FindBTNode(BTree p,KeyType k,int &i){
//反映是否在结点p中是否查找到关键字k
if(k<p->key[1]){ //结点p中查找关键字k失败
i=0;
return 0;
}
else{ //在p结点中查找
i=p->keynum;
while(k<p->key[i]&&i>1)
i--;
if(k==p->key[i]) //结点p中查找关键字k成功
return 1;
}
}


int BTNodeDelete(BTree p,KeyType k){
//在结点p中查找并删除关键字k
int i;
int found_tag; //查找标志
if(p==NULL)
return 0;
else{
found_tag=FindBTNode(p,k,i); //返回查找结果
if(found_tag==1){ //查找成功
if(p->ptr[i-1]!=NULL){ //删除的是非叶子结点
Substitution(p,i); //寻找相邻关键字(右子树中最小的关键字)
BTNodeDelete(p->ptr[i],p->key[i]); //执行删除操作
}
else
Remove(p,i); //从结点p中位置i处删除关键字
}
else
found_tag=BTNodeDelete(p->ptr[i],k); //沿孩子结点递归查找并删除关键字k
if(p->ptr[i]!=NULL)
if(p->ptr[i]->keynum<Min) //删除后关键字个数小于MIN
AdjustBTree(p,i); //调整B树
return found_tag;
}
}


void BTreeDelete(BTree &t,KeyType k){
//构建删除框架,执行删除操作
BTNode *p;
int a=BTNodeDelete(t,k); //删除关键字k
if(a==0) //查找失败
printf(" 关键字%d不在B树中\n",k);
else if(t->keynum==0){ //调整
p=t;
t=t->ptr[0];
free(p);
}
}


void DestroyBTree(BTree &t){
//递归释放B树
int i;
BTNode* p=t;
if(p!=NULL){ //B树不为空
for(i=0;i<=p->keynum;i++){ //递归释放每一个结点
DestroyBTree(*&p->ptr[i]);
}
free(p);
}
t=NULL;
}

int print(BTree p)
{
if(p==NULL)
return 0;
BTree q;
printf("[");
for(int i = 1;i <= p->keynum;i++)
{
printf("%d ",p->key[i]);
}
printf("]");
for(int i = 0;i < p->keynum+1;i++)
{
if(p->ptr[i]!=NULL)
{
q = p->ptr[i];
print(q);
}
}
}

void Test(){
BTree T=NULL;
Result s; //设定查找结果

KeyType a[]={45,24,70,7,30,53,90,3,12,26,37,50,61,85,100};
printf("创建一棵%d阶B树:\n",m);
for(int j=0;j<15;j++){ //逐一插入元素
s=SearchBTree(T,a[j]);
if(s.tag==0)
InsertBTree(T,s.i,a[j],s.pt);
printf(" \n第%d步,插入元素%d:\n ",j+1,a[j]);
print(T);
}

printf("\n");
printf("删除操作:\n");
KeyType k; //删除操作
k=7;
BTreeDelete(T,k);
printf(" 删除%d:\n ",k);
printf(" 删除后的B树: \n");
print(T);
printf("\n");

k=100;
BTreeDelete(T,k);
printf(" 删除%d:\n ",k);
printf(" 删除后的B树: \n");
print(T);
printf("\n");

printf(" 递归释放B树\n"); //递归释放B树
DestroyBTree(T);
print(T);
}

int main(){
Test();
return 0;
}

运行结果:

在这里插入图片描述
返回顶部
*[⌈m/2⌉]:向上取整符号。

------------- Thank you for reading -------------

Title - Artist
0:00