B树的插入都是在叶子结点进行的,由于B树的结点中关键码的个数是有限制的,最小度数为M的B树的结点个数是从M-1到2M-1个。比如下图是最小度数为2的B树(又称为2-3树),如下图所示,它的结点的个数就是1-3个。
先定位到要插入的位置,如果叶子结点的关键码个数还没达到上限,比如插入32,就比较简单,直接插入就行;如果叶子结点的关键码数到达了上限,就要分裂成 2个子结点,把中间的关键码往上放到父节点中。但有极端的情况,就是父结点也是满的,就需要再次分裂,可能最后要把根结点也分裂了。但是这种算法不太好实 现。
在《算法导论》中实现用的是另外一种思想,就是先分裂,在查找插入位置的过程中,如果发现有满的结点,就先把它分裂了,这就保证了在最后叶结点上插入数据的时候,这个叶结点的父结点总是不满的。下面我们看一个例子:
我们用逐个结点插入的方法创建一棵B树,结点顺序分别是{18, 31, 12, 10, 15, 48, 45, 47, 50, 52, 23, 30, 20},我们看看具体过程:
1.创建一个空的B树;
2.插入18,这时候是非满的,如下所示:
4.插入10,这时候根结点是满的,就要分裂,由于根结点比较特殊,没有父结点,就要单独处理,先生成一个空结点做为新的根结点,再进行分裂,如下所示:
6.插入47,这次叶结点满了,就要先分裂,再插入,如下所示:
其他都是同样的道理,就不赘述了,下面是源码,加入到btree.c中,最后写了个main函数和一个广度优先显示树的方法,大家可以自己对比结果,代码的实现参照了《算法导论》和博客
http://hi.baidu.com/kurt023/blog/item/4c368d8b51c59ed3fc1f10cc.html
他博客里面已经实现了,只是在定义B树的时候指针数和关键码数成一样了,我于是自己重写了一下。
1 | //函数目的:分裂存储数达到最大的节点 |
2 | void btreeSplitChild( struct btnode *parent, int pos, struct btnode *child){ |
3 | struct btnode *child2; |
4 | int i; |
5 | //为新分裂出的节点分配空间 |
6 | child2 = allocateNode(child2); |
7 | //与被分裂点同级 |
8 | child2->isleaf = child->isleaf; |
9 | //设置节点数 |
10 | child2->keyNum = M-1; |
11 |
12 | //复制数据 |
13 | for (i=0; i |
14 | child2->k[i] = child->k[i+M]; |
15 | //如果不是叶节点,复制指针 |
16 | if (!child->isleaf) |
17 | for (i=0; i |
18 | child2->p[i] = child->p[i+M]; |
19 | child->keyNum = M-1; |
20 |
21 | //将中间数作为索引插入到双亲节点中 |
22 | //插入点后面的关键字和指针都往后移动一个位置 |
23 | for (i=parent->keyNum; i>pos; i--){ |
24 | parent->k[i] = parent->k[i-1]; |
25 | parent->p[i+1] = parent->p[i]; |
26 | } |
27 | parent->k[pos] = child->k[M-1]; |
28 | parent->keyNum++; |
29 | parent->p[pos+1] = child2; |
30 | } |
31 |
32 | /* 函数目的:向非满的节点中插入一个数据 |
33 | * 注意:插入前保证key在原来的B树中不存在 |
34 | */ |
35 | void btreeInsertNoneFull( struct btnode *ptr, int data){ |
36 | int i; |
37 | struct btnode *child; //要插入结点的子结点 |
38 | i = ptr->keyNum; |
39 | //如果是叶节点,直接插入数据 |
40 | if (ptr->isleaf){ |
41 | while ((i>0) && (datak[i-1])){ |
42 | ptr->k[i] = ptr->k[i-1]; |
43 | i--; |
44 | } |
45 | //插入数据 |
46 | ptr->k[i] = data; |
47 | ptr->keyNum++; |
48 | } |
49 | else { //不是叶节点,找到数据应插入的子节点并插入 |
50 | while ((i>0) && (datak[i-1])) |
51 | i--; |
52 | child = ptr->p[i]; |
53 | if (child->keyNum == 2*M-1){ |
54 | btreeSplitChild(ptr, i, child); |
55 | if (data > ptr->k[i]) |
56 | i++; |
57 | } |
58 | child = ptr->p[i]; |
59 | btreeInsertNoneFull(child, data); //在子树中递归 |
60 | } |
61 | } |
62 |
63 | /* 插入一个结点 */ |
64 | struct btnode * btreeInsert( struct btnode *root, int data){ |
65 | struct btnode * new ; |
66 | /* 检查是否根节点已满,如果已满,分裂并生成新的根节点 */ |
67 | if (root->keyNum == 2*M-1){ |
68 | new = allocateNode( new ); |
69 | new ->isleaf = 0; |
70 | new ->keyNum = 0; |
71 | new ->p[0] = root; |
72 | btreeSplitChild( new , 0, root); |
73 | btreeInsertNoneFull( new , data); |
74 | return new ; |
75 | } |
76 | else { //还没到最大数据数,直接插入 |
77 | btreeInsertNoneFull(root, data); |
78 | return root; |
79 | } |
80 | } |
81 |
82 | //函数目的:广度优先显示树 |
83 | void btreeDisplay( struct btnode *root){ |
84 | int i, queueNum=0; |
85 | int j; |
86 | struct btnode *queue[20]; |
87 | struct btnode *current; |
88 |
89 | //加入队列 |
90 | queue[queueNum] = root; |
91 | queueNum++; |
92 |
93 | while (queueNum>0){ |
94 | //出队 |
95 | current = queue[0]; |
96 | queueNum--; |
97 | //移出第一个元素后后面的元素往前移动一个位置 |
98 | for (i=0; i |
99 | queue[i] = queue[i+1]; |
100 | //显示节点 |
101 | j = current->keyNum; |
102 | printf ( "[ " ); |
103 | for (i=0; i |
104 | printf ( "%d " , current->k[i]); |
105 | } |
106 | printf ( "] " ); |
107 |
108 | //子节点入队 |
109 | if (current!=NULL && current->isleaf!=1){ |
110 | for (i=0; i<=(current->keyNum); i++){ |
111 | queue[queueNum] = current->p[i]; |
112 | queueNum++; |
113 | } |
114 | } |
115 | } |
116 | printf ( "\n" ); |
117 | } |
118 |
119 | int main() |
120 | { |
121 | struct btnode *root; |
122 | int a[13] = {18, 31, 12, 10, 15, 48, 45, 47, 50, 52, 23, 30, 20}; |
123 | int i; |
124 |
125 | root = btreeCreate(root); |
126 | for (i=0; i<13; i++){ |
127 | root = btreeInsert(root, a[i]); |
128 | btreeDisplay(root); |
129 | } |
130 |
131 | return 0; |
132 | } |
运行结果:
同样一批关键码用不同算法生成的B树可能是不同的,比如4个关键码的结点[1,2,3,4]分裂的时候,把2或3放上去都可以;同样的算法插入顺序不同也可能不同。
附件中是源码,在Linux下编译通过。