B-树

B-树

一、B-树是什么?

B-树是一棵所有叶子节点高度都相同的平衡多叉树。

其结构类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
                                    ____ ___ ___
| 24 | | |
|
---------------------------------------------
| |
____ ____ ___ ____ ___ ___
| 15 | 20 | | | 33 | | |
| |
----------------------------------- -------------------
| | | | |
____ ____ ___ ____ ___ ___ ____ ____ ___ ____ ____ ___ ____ ___ ___
| 10 | 12 | | | 18 | | | | 21 | 23 | | | 30 | 31 | | | 38 | | |

B-树的一些性质:

  • 树节点中,子节点数量的最大值称为B-树的阶
  • m 阶B-树的节点,最多拥有 m - 1 个元素
  • B-树的所有叶子节点都在同一层,是高度平衡的
  • 除根节点外,所有节点的元素个数不少于 Math.ceil(m/2) - 1,也即子节点数量不少于 Math.ceil(m/2)
  • 节点的大小,始终满足 左子节点 < 父节点 < 右子节点,即和二叉查找树类似

简单来说,B-树的结构是节点的子节点数量要半满,叶子节点的高度要始终一样。

二、为什么要用B-树?

  • B-树或B-树变体被广泛应用于需要进行插入、删除、范围检索等的应用程序,比如 MySQL 数据库索引
  • 更新和检索操作只影响部分节点,对于基于磁盘的检索,可以减少 I/O 请求,提升性能
  • B-树保证至少有一定比例的节点是满的,能改进空间利用率

三、怎么实现一棵B-树?

B-树主要包含几种操作:

  • 检索元素:在树中搜索指定的元素
  • 插入元素:往树中插入新的元素
  • 删除元素:从树中删除指定的元素

其中插入和删除会对B-树的结构产生影响,稍微麻烦亿点。

3.1 检索元素

B-树和二叉搜索树类似,二叉搜索树使用的是二分查找,而B-树也是类似的:

  • B-树检索元素采用的是二分查找的升级版本 —— 多路查找

比如说,上面的B-树中,要寻找元素 23,那么它的搜索路线就是:

  • 24 -> 20 -> 23

在B-树中查找的话,就是这样子:

1
2
3
4
5
6
7
8
9
10
11
12
                                    ____ ___ ___
| 24 | | |
|
---------------------------------------------
| |
____ ____ ___ ____ ___ ___
| | 20 | | | | | |
| |
----------------------------------- -------------------
| | | | |
____ ____ ___ ____ ___ ___ ____ ____ ___ ____ ____ ___ ____ ___ ___
| | | | | | | | | | 23 | | | | | | | | | |

对应到伪代码的话,差不多是这样的:

1
2
3
4
5
6
7
8
9
10
private V get(BTNode<K, V> root, K key) {
int index = root.findIndex(key);
if (key.equals(root.getKey(index))) {
// 找到 key 对应的节点
return root.getValue(index);
} else {
// 往子树继续找 key 对应的节点
return get(root.getChild(index), key);
}
}

实际就是从上往下多路递归查找元素。

3.2 插入元素

B-树和二叉搜索树的插入类似:

  • B-树插入新元素,始终是在叶子节点插入

插入元素,可以分为 2 种情况处理:

  1. 节点空间足够,直接插入元素
  2. 节点空间不足,1 个节点分裂成 3 个节点,执行上溢操作

这 2 种情况的效果如下。

3.2.1 叶子节点-直接插入

当叶子节点空间足够时,可以直接插入新元素,而且不会影响到B-树的高度平衡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 ____ ____ ___ 
| 12 | | |

| 插入 20
v

____ ____ ___
| 12 | 20 | |

| 插入 9
v

____ ____ ____
| 9 | 12 | 20 |

空间足够时,直接插入元素就行了。

3.2.2 叶子节点-上溢分裂

当叶子节点空间不足时,此时再插入新元素,会导致节点空间上溢,然后分裂:

1
2
3
4
5
6
7
8
9
10
11
12
13
           ___ ____ ____ 
| 9 | 12 | 20 |

| 插入 25
v

____ ___ ___
| 20 | | |
|
---------------------
| |
___ ____ ___ ____ ____ ____
| 9 | 12 | | | 25 | | |

节点元素上溢后,1 个节点会分裂成 3 个节点。

3.2.3 内部节点-直接插入

为了保持B-树的高度平衡,叶子节点会将分裂后的父节点,往上插入之前的父节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
                     ____ ___ ___ 
| 20 | | |
|
----------------------
| |
___ ____ ____ ____ ____ ____
| 9 | 12 | 18 | | 25 | | |


| 插入 10
v

____ ____ ___
| 12 | 20 | |
|
--------------------------------------------
| | |
___ ____ ____ ____ ____ ____ ____ ____ ____
| 9 | 10 | | | 18 | | | | 25 | | |

左子节点 [9, 12, 18] 插入新节点 10 会产生上溢,节点 12 会上溢父节点中。

因为内部节点 [20] 空间足够,所以 12 直接插入即可。

3.2.4 内部节点-分裂上溢

子节点上溢后,如果父节点空间也满了,那么父节点也会出现分裂上溢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
                                           ____ ____ ____ 
| 20 | 30 | 45 |
|
-------------------------------------------------------------------
| | | |
___ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____
| 9 | 12 | 18 | | 25 | | | | 37 | 39 | | | 62 | 85 | |


| 插入 10
v

____ ____ ____
| 30 | | |
|
----------------------------------------------------------
| |
____ ____ ___ ____ ___ ___
| 12 | 20 | | | 45 | | |
| |
-------------------------------------------- ------------------------
| | | | |
___ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____
| 9 | 10 | | | 18 | | | | 25 | | | | 37 | 39 | | | 62 | 85 | |

插入元素 10 之后,会传递性地导致节点 [9, 12, 18] 和节点 [20, 30, 45] 都产生分裂上溢。

这种分裂上溢可以一直持续到根节点,最后可能会导致B-树提升一层(如上)。

上述的 4 种情况,实际就 2 种:

  1. 空间足够时,直接插入
  2. 空间不足时,分裂上溢

对应的伪代码类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**- 插入新元素 -*/
private BTNode<K, V> put(BTNode<K, V> root, K key, V value) {
int index = root.findIndex(key);

// 叶子节点
if (root.isLeaf()) {
// 叶子节点添加元素后,可能发生了分裂
return root.add(key, value);
}

// 内部节点
BTNode<K, V> child = root.getChild(index);
child = put(child, key, value);
// 子树添加元素后,可能发生了分裂
return root.overflow(index, child);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**- 直接插入和分裂上溢(叶子节点和内部节点的插入逻辑一样) -*/
public BTNode<K, V> overflow(int index, BTNode<K, V> node) {
// 子节点没上溢
if (getChild(index) == node) {
return this;
}

// 子节点上溢了
// 1. 空间足够,直接插入
if (size + node.size() < m) {
return insertNode(index, node);
}

// 2. 空间不足,分裂节点
return splitNodes(index, node);
}

3.3 删除元素

删除元素,可以分为 3 种情况:

  1. 删除后元素数量仍然满足要求,直接删除
  2. 删除后元素数量不满足要求,看兄弟节点是否可以借一个过来
  3. 兄弟节点不能借,那就将父子节点合并成 1 个节点,产生下溢

这几种情况如下。

3.3.1 直接删除

如果节点删除元素后,元素数量仍然处于半满状态,则直接返回即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
           ____ ___ ___ 
| 20 | | |
|
----------------------
| |
___ ____ ____ ____ ____ ____
| 9 | 12 | 18 | | 25 | | |


| 删除 12
v

____ ___ ___
| 20 | | |
|
----------------------
| |
___ ____ ____ ____ ____ ____
| 9 | 18 | | | 25 | | |

叶子节点 [9, 12, 18] 删除 12 以后,元素数量依旧满足半满状态,则可以直接删除并返回。

3.3.2 从兄弟借

如果删除元素后,元素数量少于一半,不满足要求,则看一下兄弟节点能不能借一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
           ____ ___ ___ 
| 20 | | |
|
----------------------
| |
___ ____ ____ ____ ____ ____
| 9 | 12 | 18 | | 25 | | |


| 删除 25
v

____ ___ ___
| 18 | | |
|
----------------------
| |
___ ____ ____ ____ ____ ____
| 9 | 12 | | | 20 | | |

右子节点 [25] 删除 25 以后,元素数量不满足要求了,找兄弟节点 [9, 12, 18] 借一个。

但是不能直接把左子节点的元素移到右子节点中,因为B-树是要求满足 左子节点 < 父节点 < 右子节点

所以“借用”实际上是一种旋转操作,父节点转到需要借用元素的节点,而兄弟节点元素则转到父节点中。

3.3.3 父子合并

如果兄弟节点借不了元素,那就将父节点 + 左子节点 + 右子节点合并在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
           ____ ___ ___ 
| 20 | | |
|
----------------------
| |
___ ____ ____ ____ ____ ____
| 9 | | | | 25 | | |


| 删除 25
v

___ ____ ___
| 9 | 20 | |

删除元素 25 后,由于兄弟节点没办法借用,所以将父子节点都合并成 1 个节点,这个合并称为下溢。

下溢和上溢一样,也可能会持续下溢,一直持续到根节点,最终导致树的层级下降。

上述情况的伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**- 删除元素 -*/
private BTNode<K, V> remove(BTNode<K, V> root, K key) {
int index = root.findIndex(key);

// 找到对应节点后移除元素
if (key.equals(root.getKey(index))) {
// 直接删除元素后,可能导致父节点下溢
return root.delete(index);
}

// 在子树内递归移除元素
remove(root.getChild(index), key);
// 子树删除元素后,可能导致父节点下溢
return root.underflow(index);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** 从兄弟借和父子合并(叶子节点和内部节点的下溢逻辑是一样的) */
public BTNode<K, V> underflow(int index) {
// 子节点合法,无需下溢
if (isLegal(getChild(index))) {
return this;
}

// 子节点不合法,需修正
// 1. 从兄弟节点借一个元素
if (borrowLeft(index) || borrowRight(index)) {
return this;
}

// 2. 合并父元素 + 左右子节点
return mergeNodes(index);
}

总结

  • 性质
    • m 阶B-树的节点,最多拥有 m - 1 个元素
    • B-树的所有叶子节点都在同一层,是高度平衡的
    • 除根节点外,所有节点的元素个数不少于 Math.ceil(m/2) - 1,也即子节点数量不少于 Math.ceil(m/2)
    • 节点的大小,始终满足 左子节点 < 父节点 < 右子节点
  • 检索
    • 多路查找:二分查找的升级版本
  • 插入
    1. 空间足够,直接插入
    2. 空间不足,分裂上溢
  • 删除
    1. 元素够多,直接删除
    2. 元素不够,从兄弟借
    3. 兄弟节点无法借用,合并父子节点,执行下溢

参考

《数据结构与算法分析(第三版)》

https://www.cnblogs.com/nullzx/p/8729425.html

https://github.com/LiuXianghai-coder/Test-Repo/blob/master/DataStructure/BTree.java

https://blog.csdn.net/u011711997/article/details/80420392

https://blog.csdn.net/c630843901/article/details/121423196

https://blog.csdn.net/oooooorz/article/details/112297554

https://blog.csdn.net/cdnight/article/details/11772621

附录

B-树接口

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
/**
* B-树
*
* @author weijiaduo
* @since 2023/1/2
*/
public interface BTree<K extends Comparable<K>, V> {

/**
* 查找指定 key 的值
*
* @param key key
* @return value
*/
V get(K key);

/**
* 添加指定 key-value 的节点
*
* @param key key
* @param value value
*/
void put(K key, V value);

/**
* 删除指定 key 的节点
*
* @param key key
*/
void remove(K key);

}

B-树实现

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
/**
* B-树实现类
*
* @author weijiaduo
* @since 2023/1/2
*/
public class BTreeImpl<K extends Comparable<K>, V> implements BTree<K, V> {

/**
* 根节点
*/
private BTNode<K, V> root;
/**
* m 阶 B 树
*/
private final int m;

public BTreeImpl(int m) {
this.m = m;
}

@Override
public V get(K key) {
return get(root, key);
}

/**
* 从当前节点开始找指定 key 的值
*
* @param root 当前根节点
* @param key key
* @return value
*/
private V get(BTNode<K, V> root, K key) {
if (root == null) {
return null;
}

// 查找当前 key 所在的位置
int index = root.findIndex(key);
if (key.equals(root.getKey(index))) {
// 找到 key 对应的节点
return root.getValue(index);
} else {
// 往子树继续找 key 对应的节点
return get(root.getChild(index), key);
}
}

@Override
public void put(K key, V value) {
root = put(root, key, value);
}

/**
* 在当前树内添加指定的 key-value
*
* @param root 当前根节点
* @param key key
* @param value value
* @return 新的根节点
*/
private BTNode<K, V> put(BTNode<K, V> root, K key, V value) {
// 1. 整棵树为空时,插入新节点
if (root == null) {
BTNode<K, V> node = new BTNode<>(m);
return node.add(key, value);
}

// 验证是否已存在,存在则只需更新
int index = root.findIndex(key);
if (key.equals(root.getKey(index))) {
// 更新节点
root.setValue(index, value);
return root;
}

// 2. 叶子节点,直接添加
if (root.isLeaf()) {
// 添加到叶子节点中,有可能发生分裂
return root.add(key, value);
}

// 3. 内部节点,添加到子树的叶子节点中
BTNode<K, V> child = root.getChild(index);
child = put(child, key, value);

// 添加元素后子树可能发生了分裂
return root.overflow(index, child);
}

@Override
public void remove(K key) {
root = remove(root, key);
if (root != null && root.isEmpty()) {
// 树的高度下降
root = root.firstChild();
}
}

/**
* 在当前树内删除指定 key 的节点
*
* @param root 当前根节点
* @param key key
* @return 新的根节点
*/
private BTNode<K, V> remove(BTNode<K, V> root, K key) {
if (root == null) {
return null;
}

// 找到对应节点后移除元素
int index = root.findIndex(key);
if (key.equals(root.getKey(index))) {
return root.delete(index);
}

// 在子树内递归移除元素
remove(root.getChild(index), key);

// 移除后可能需要父节点下溢
return root.underflow(index);
}

}

B-树节点

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
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
/**
* B-树节点
*
* @author weijiaduo
* @since 2023/1/2
*/
public class BTNode<K extends Comparable<K>, V> {

/**
* m 阶 B 树
*/
private final int m;
/**
* 节点元素阈值
*/
private final int threshold;
/**
* 节点元素数组
* <p>
* 元素的第 0 位始终是一个占位元素,专门用于存放最左子节点
* <p>
* 所以一个节点能拥有的元素个数最多是 m - 1
*/
private final Object[] elements;
/**
* 实际元素数量
* <p>
* 取值范围是 [0, m - 1]
*/
private int size;

public BTNode(int m) {
this.m = m;
this.threshold = half(m);
elements = new Object[m];
// 最左元素占位,用于存放最左子节点指针
elements[0] = new Entry<K, V>(null, null);
size = 0;
}

/**
* @return 元素数量
*/
public int size() {
return size;
}

/**
* 半数阈值 ceil(m / 2) - 1
*
* @param m 当前值
* @return 半数阈值
*/
private int half(int m) {
return (m + 1) / 2 - 1;
}

/**
* 是否已满
*
* @return true已满/false未满
*/
public boolean isFull() {
return size == m - 1;
}

/**
* 是否为空
*
* @return true为空/false非空
*/
public boolean isEmpty() {
return size == 0;
}

/**
* 节点是否合法
* <p>
* 节点的元素数量要求大于等于阈值
*
* @return true合法/false非法
*/
public boolean isLegal() {
return size >= threshold;
}

/**
* 是否可借用元素给别的节点
* <p>
* 当节点的元素数量大于阈值时,才可以外借元素
*
* @return true可借用/false不可借用
*/
private boolean canBorrow() {
return size > threshold;
}

/**
* 是否是叶子节点
* <p>
* 没有任何一个非空子节点时才是叶子节点
*
* @return true叶子节点/false内部节点
*/
public boolean isLeaf() {
for (int i = 0; i <= size; i++) {
if (getChild(i) != null) {
return false;
}
}
return true;
}

/**
* 获取所有的元素
*
* @return 元素集合
*/
public List<Entry<K, V>> entries() {
List<Entry<K, V>> entries = new ArrayList<>(size);
// 元素从索引 1 开始
for (int i = 1; i <= size; i++) {
entries.add(getEntry(i));
}
return entries;
}

/**
* 获取所有的 key
*
* @return key 集合
*/
public List<K> keys() {
List<K> keys = new ArrayList<>(size);
// 元素从索引 1 开始
for (int i = 1; i <= size; i++) {
keys.add(getKey(i));
}
return keys;
}

/**
* 获取所有的 value
*
* @return value 集合
*/
public List<V> values() {
List<V> values = new ArrayList<>(size);
// 元素从索引 1 开始
for (int i = 1; i <= size; i++) {
values.add(getValue(i));
}
return values;
}

/**
* 获取指定位置的 key
*
* @param index 索引
* @return key
*/
public K getKey(int index) {
return getEntry(index).key;
}

/**
* 获取指定位置的 value
*
* @param index 索引
* @return value
*/
public V getValue(int index) {
return getEntry(index).value;
}

/**
* 设置指定位置的 value
*
* @param index 索引
* @param value 值
*/
public void setValue(int index, V value) {
getEntry(index).value = value;
}

/**
* 获取所有的子节点
* <p>
* 注意:是所有可能的子节点,也包括了 null 子节点
*
* @return 子节点集合
*/
public List<BTNode<K, V>> children() {
List<BTNode<K, V>> children = new ArrayList<>(size + 1);
// 子节点从索引 0 开始
for (int i = 0; i <= size; i++) {
children.add(getChild(i));
}
return children;
}

/**
* 返回第一个子节点
* <p>
* 注意:不是第一个非空子节点,而是第 1 个元素的左子节点
*
* @return 第一个子节点
*/
public BTNode<K, V> firstChild() {
return getChild(0);
}

/**
* 返回最后一个子节点
* <p>
* 注意:不是最后一个非空子节点,而是最后 1 个元素的右子节点
*
* @return 最后一个子节点
*/
public BTNode<K, V> lastChild() {
return getChild(size);
}

/**
* 获取指定位置的下一层子节点
*
* @param index 指定位置
* @return 下一层子树根节点
*/
public BTNode<K, V> getChild(int index) {
return getEntry(index).pointer;
}

/**
* 更新指定位置的子节点
*
* @param index 索引
* @param node 子节点
*/
public void setChild(int index, BTNode<K, V> node) {
getEntry(index).pointer = node;
}

/**
* 查找指定 key 的位置
* <p>
* 返回最后一个小于等于 key 的位置
*
* @param key key
* @return key 的位置
*/
public int findIndex(K key) {
// 第 0 位是占位元素,还没算在内
return 1 + binaryle(keys(), key);
}

/**
* 二分查找,返回最后一个小于等于 key 的位置
*
* @param keys key 集合
* @param key 指定 key
* @return 最后一个小于等于 key 的位置/-1
*/
private int binaryle(List<K> keys, K key) {
int size = keys.size();
int l = 0, r = size - 1;
while (l <= r) {
int m = l + (r - l) / 2;
K k = keys.get(m);
if (k.compareTo(key) <= 0) {
if (m == size - 1 || keys.get(m + 1).compareTo(key) > 0) {
return m;
}
l = m + 1;
} else {
r = m - 1;
}
}
return -1;
}

/**
* 添加新元素
*
* @param key key
* @param value value
*/
public BTNode<K, V> add(K key, V value) {
BTNode<K, V> node = new BTNode<>(m);
node.addEntry(new Entry<>(key, value));
int index = findIndex(key);
return overflow(index, node);
}

/**
* 节点上溢处理
* <p>
* 子树添加新元素后,可能会分裂成新节点,替代原有的子节点
*
* @param index 索引
* @param node 新节点
* @return 添加后的当前节点
*/
public BTNode<K, V> overflow(int index, BTNode<K, V> node) {
if (node == null || node.isEmpty()) {
return this;
}

// 还是旧节点,说明子节点没有上溢,无需处理
BTNode<K, V> cur = getChild(index);
if (cur == node) {
return this;
}

// 1. 当前空间足够插入
if (size + node.size() < m) {
// 新子节点的左子节点替代旧子节点的位置
setChild(index, node.firstChild());
for (Entry<K, V> entry : node.entries()) {
insertEntry(++index, entry);
}
return this;
}

// 2. 当前空间不够,需要分裂成 3 个节点
return splitNodes(index, node);
}

/**
* 子节点上溢后,父节点已满,则需要分裂节点,将 1 个节点拆分成 3 个节点
*
* @param index 上溢子节点的父元素索引
* @param newNode 上溢的子节点
* @return 分裂后新的父节点
*/
private BTNode<K, V> splitNodes(int index, BTNode<K, V> newNode) {
// 新子节点的左子节点替代旧子节点的位置
setChild(index, newNode.firstChild());

int newSize = size + newNode.size();
List<Entry<K, V>> allEntries = new ArrayList<>(newSize);
List<Entry<K, V>> curEntries = entries();
for (int i = 0; i < index && i < size; i++) {
allEntries.add(curEntries.get(i));
}
allEntries.addAll(newNode.entries());
for (int i = index; i < size; i++) {
allEntries.add(curEntries.get(i));
}

// 根节点
int mid = newSize / 2;
BTNode<K, V> root = new BTNode<>(m);
for (int i = mid; i <= mid; i++) {
root.addEntry(allEntries.get(i));
}
// 左子节点
BTNode<K, V> left = new BTNode<>(m);
for (int i = 0; i < mid; i++) {
left.addEntry(allEntries.get(i));
}
// 右子节点
BTNode<K, V> right = new BTNode<>(m);
for (int i = mid + 1; i < newSize; i++) {
right.addEntry(allEntries.get(i));
}

// 边界指针
left.setChild(0, firstChild());
right.setChild(0, root.lastChild());
root.setChild(0, left);
root.setChild(root.size, right);
return root;
}

/**
* 删除指定位置的元素
* <p>
* 删除元素后,节点可能会产生下溢,从而返回了一个空元素节点
*
* @param index 索引
* @return 删除后的根节点
*/
public BTNode<K, V> delete(int index) {
if (isEmpty() || index == 0) {
throw new IllegalStateException(String.format("index: %d, size: %d", index, size));
}

if (isLeaf()) {
// 叶子节点
// 直接删除,删除后可能会变成元素为空的空节点
// 空节点将由父节点的 underflow 处理掉,或者由根节点处理掉
removeEntry(index);
return this;
}

// 内部节点
// 使用前驱或后驱进行替换
int rpIndex = getReplacer(this, index);
BTNode<K, V> child = getChild(rpIndex);
BTNode<K, V> newChild;
if (rpIndex < index) {
// 前驱元素替换
Entry<K, V> max = max(child);
newChild = removeMax(child);
setChild(rpIndex, newChild);
setEntry(index, max);
} else {
// 后驱元素替换
Entry<K, V> min = min(child);
newChild = removeMin(child);
setChild(rpIndex, newChild);
setEntry(index, min);
}

// 移除后可能需要父节点下溢
return underflow(rpIndex);
}

/**
* 节点下溢处理
* <p>
* 存在非法子节点时,可能需要对父节点进行下溢操作
*
* @param index 父节点索引
* @return 根节点
*/
public BTNode<K, V> underflow(int index) {
// 验证子节点是否合法
BTNode<K, V> cur = getChild(index);
if (cur == null || cur.isLegal()) {
return this;
}

// 1. 从兄弟节点借一个元素
BTNode<K, V> left = null;
if (index > 0) {
left = getChild(index - 1);
if (left != null && left.canBorrow()) {
return borrowLeft(index);
}
}
BTNode<K, V> right = null;
if (index < size) {
right = getChild(index + 1);
if (right != null && right.canBorrow()) {
return borrowRight(index);
}
}
if (left == null && right == null) {
return this;
}

// 2. 合并父元素 + 左右子节点
// 始终把当前节点作为合并时的右子节点
return mergeNodes(left != null ? index : index + 1);
}

/**
* 借用左子节点的值
* <p>
* 实际就是右旋,将父节点转到右子节点,左子节点的元素转到父节点
*
* @param index 父节点索引
*/
private BTNode<K, V> borrowLeft(int index) {
BTNode<K, V> left = getChild(index - 1);
BTNode<K, V> right = getChild(index);

Entry<K, V> parentEntry = getEntry(index);
Entry<K, V> leftEntry = left.getEntry(left.size);
BTNode<K, V> leftRight = left.lastChild();

// 父节点转到右子节点
right.insertEntry(1, parentEntry);
right.setChild(1, right.firstChild());
// 左子节点转到父节点
left.removeEntry(left.size);
setEntry(index, leftEntry);
setChild(index, right);
right.setChild(0, leftRight);
return this;
}

/**
* 借用右子节点的值
* <p>
* 实际就是左旋,将父元素转到左子节点,右子节点的元素转到父节点
*
* @param index 父节点索引
*/
private BTNode<K, V> borrowRight(int index) {
BTNode<K, V> right = getChild(index + 1);
BTNode<K, V> left = getChild(index);

Entry<K, V> parentEntry = getEntry(index + 1);
Entry<K, V> rightEntry = right.getEntry(1);
BTNode<K, V> rightLeft = right.firstChild();

// 父节点转到左子节点
left.addEntry(parentEntry);
// 右子节点转到父节点
right.setChild(0, right.getChild(1));
right.removeEntry(1);
setEntry(index + 1, rightEntry);
setChild(index + 1, right);
left.setChild(left.size, rightLeft);
return this;
}

/**
* 父节点元素 + 右子节点,全都合并到左子节点
* <p>
* 因为父节点元素和右子节点是一一对应的,所以都是按照“父 + 右 --> 左”进行合并
* <p>
* 方便同时删除父节点元素和右子节点
*
* @param index 父元素索引
*/
private BTNode<K, V> mergeNodes(int index) {
BTNode<K, V> left = getChild(index - 1);
BTNode<K, V> right = getChild(index);

// 父节点元素合并到左子节点
Entry<K, V> parentEntry = getEntry(index);
left.addEntry(parentEntry);

// 右子节点合并到左子节点
left.setChild(left.size, right.firstChild());
for (Entry<K, V> entry : right.entries()) {
left.addEntry(entry);
}

// 移除父节点元素
removeEntry(index);
return this;
}

/**
* 获取可替换子节点索引(前驱/后驱)
*
* @param root 当前根节点
* @param index 当前索引
* @return 可替换子节点索引
*/
private int getReplacer(BTNode<K, V> root, int index) {
BTNode<K, V> right = null;
if (index <= root.size) {
right = root.getChild(index);
}
if (right == null) {
return index - 1;
}
BTNode<K, V> left = null;
if (index > 0) {
left = root.getChild(index - 1);
}
if (left == null) {
return index;
}

if (left.canBorrow()) {
return index - 1;
} else {
return index;
}
}

/**
* 获取最大值
*
* @param root 当前根节点
* @return 最大值
*/
private Entry<K, V> max(BTNode<K, V> root) {
if (root == null) {
return null;
}

if (root.isLeaf()) {
return root.getEntry(root.size);
}
return max(root.lastChild());
}

/**
* 移除最大值
*
* @param root 当前根节点
* @return 移除后的根节点
*/
private BTNode<K, V> removeMax(BTNode<K, V> root) {
if (root == null) {
return null;
}

if (root.isLeaf()) {
root.removeEntry(root.size);
return root;
}

int index = root.size;
removeMax(root.getChild(index));
return root.underflow(index);
}

/**
* 获取最小值
*
* @param root 当前根节点
* @return 最小值
*/
private Entry<K, V> min(BTNode<K, V> root) {
if (root == null) {
return null;
}

if (root.isLeaf()) {
return root.getEntry(1);
}
return min(root.firstChild());
}

/**
* 移除最小值
*
* @param root 当前根节点
* @return 移除值后的根节点
*/
private BTNode<K, V> removeMin(BTNode<K, V> root) {
if (root == null) {
return null;
}

if (root.isLeaf()) {
root.removeEntry(1);
return root;
}

int index = 0;
removeMin(root.getChild(index));
return root.underflow(index);
}

/**
* 获取指定位置的元素
*
* @param index 索引
* @return 元素
*/
private Entry<K, V> getEntry(int index) {
return (Entry<K, V>) elements[index];
}

/**
* 更新指定位置的元素
* <p>
* 要求原始元素必须存在,否则应该使用 {@code insertEntry()}
*
* @param index 索引
* @param entry 元素
*/
private void setEntry(int index, Entry<K, V> entry) {
Entry<K, V> oldEntry = getEntry(index);
if (oldEntry == null || index == 0) {
throw new IllegalStateException(String.format("index: %d, size: %d", index, size));
}

// 保留子节点,只替换元素的 key-value
elements[index] = entry;
entry.pointer = oldEntry.pointer;
}

/**
* 追加新元素
* <p>
* 此方法不会产生节点分裂,需要分裂时应调用 {@code overflow()} 方法
*
* @param entry 元素
*/
private void addEntry(Entry<K, V> entry) {
if (isFull()) {
throw new IllegalStateException(String.format("size: %d", size));
}

elements[++size] = entry;
}

/**
* 插入新元素
* <p>
* 此方法不会产生节点分裂,需要分裂时应调用 {@code overflow()} 方法
*
* @param index 插入索引
* @param entry 元素
*/
private void insertEntry(int index, Entry<K, V> entry) {
if (isFull() || index == 0) {
throw new IllegalStateException(String.format("index: %d, size: %d", index, size));
}

if (size >= index) {
System.arraycopy(elements, index, elements, index + 1, size - index + 1);
elements[index] = entry;
size++;
} else {
elements[index] = entry;
size = index;
}
}

/**
* 移除指定位置的元素
*
* @param index 索引
*/
private void removeEntry(int index) {
if (isEmpty() || index == 0) {
throw new IllegalStateException(String.format("index: %d, size: %d", index, size));
}

if (size > index) {
System.arraycopy(elements, index + 1, elements, index, size - index);
}
elements[size] = null;
size--;
}

@Override
public String toString() {
String[] s = new String[size];
for (int i = 1; i <= size; i++) {
s[i - 1] = elements[i].toString();
}
return "[" + String.join(", ", s) + "]";
}

/**
* 单个元素
*/
static class Entry<K extends Comparable<K>, V> {
/**
* key
*/
K key;
/**
* value
*/
V value;
/**
* 右子节点指针,即大于 key 的子树
*/
BTNode<K, V> pointer;

public Entry(K key, V value) {
this.key = key;
this.value = value;
pointer = null;
}

@Override
public String toString() {
return "{" + key + " : " + value + "}";
}
}

}
作者

jiaduo

发布于

2023-01-12

更新于

2023-04-03

许可协议