1. 简介
- 如果数据太多导致内存装不下,则必须把数据结构存放到磁盘上,此时大 O O O 模型不再适用。
- 磁盘的访问代价相比于 CPU 计算和内存访问来说实在是太高了,因此宁愿执行大量的计算,也要减少磁盘 I/O。
- 不平衡二叉树在最坏的情况下具有线性的深度,因此如果将其作为一种磁盘数据结构,则最坏情况下仍需进行大量的磁盘 I/O。
2. 约束
一棵 m m m 阶 B 树具有如下特点:
(1)根节点要么是一个叶节点,要么至少具有两个孩子节点;
(2)除根节点以外,每个内部节点都具有
[
⌈
m
/
2
⌉
,
m
]
[\lceil m/2\rceil, m]
[⌈m/2⌉,m] 个孩子节点,因此具有
[
⌈
m
/
2
⌉
−
1
,
m
−
1
]
[\lceil m/2\rceil -1, m-1]
[⌈m/2⌉−1,m−1] 个关键码;
(3)所有叶节点都处在树的同一层,也具有
[
⌈
m
/
2
⌉
−
1
,
m
−
1
]
[\lceil m/2\rceil -1, m-1]
[⌈m/2⌉−1,m−1] 个关键码。
(4)每个节点都会按序存储关键码,介于关键码
k
e
y
1
key1
key1 和
k
e
y
2
key2
key2 之间的指针所指孩子节点所存储的关键码的范围为
(
k
e
y
1
,
k
e
y
2
)
(key1, key2)
(key1,key2)。
不论是内部节点还是叶节点,都会存储关键码及其对应的记录。
通常情况下,要使 B 树一个节点的大小能填满一个磁盘块,节点中的“指针”实际上就是孩子节点所在的磁盘块号。
3. 搜索
(1)从根节点开始;
(2)在当前节点中对关键码进行二分搜索,如果找到,则返回相应的记录;
(3)如果当前节点是叶节点且没有找到目标,则失败返回;
(4)否则,沿着相应的分支继续往下,然后继续回到步骤(2)。
在最好的情况下,关键码位于根节点中,此时搜索操作的只需执行一次磁盘 I/O;最坏情况下,关键码位于叶节点中,此时需执行的 I/O 操作等于树的高度。通常情况下,可以将 B 树的树根常驻于内存中,此时可以减少一次的磁盘 I/O。
设关键码总数为 N N N,则一棵 m m m 阶 B 树的高度为 log m N \log_m N logmN。
4. 插入
(1)首先找到应当包含待插入关键码的叶节点;
(2)如果目标叶节点还有空闲的空间,则插入关键码,然后成功返回;
(3)否则,将叶节点分裂成两个节点,并把中间的关键码提升到父节点;
(4)如果父节点也已经满了,则继续分裂父节点,并再次提升中间的关键码,重复此过程,直至完成插入;此过程可能会增长树的高度。
5. 删除
(1)如果要删除的关键码
A
A
A 位于内部节点中,则用其后继关键码
B
B
B(关键码
A
A
A 右边的指针所指子树中的最小的关键码,一定位于叶节点上)覆盖要删除的关键码,然后删除叶节点中相应的关键码
B
B
B;如果
A
A
A 位于叶节点上,则直接删除之;设包含待删除关键码的叶节点为
x
x
x;
(2)如果删除关键码后,叶节点
x
x
x 中关键码个数大于
⌈
m
/
2
⌉
−
1
\lceil m/2\rceil -1
⌈m/2⌉−1,则成功返回,否则继续往下执行;
(3)如果
x
x
x 的右兄弟(左兄弟)节点中关键码的个数大于
⌈
m
/
2
⌉
−
1
\lceil m/2\rceil -1
⌈m/2⌉−1,则将右兄弟(左兄弟)节点中最小(最大)的关键码
C
C
C 上移到父节点中,然后将父节点中小于(大于)关键码
C
C
C 且与
C
C
C 紧邻在一起的关键码下移到叶节点
x
x
x 中,然后成功返回,否则继续往下执行;
(4)将
x
x
x 与右兄弟(左兄弟)节点合并,并将父节点中指向该兄弟节点的的指针的左边(右边)一个关键码下移到合并后的节点中;如果此操作导致父节点发生了下溢(关键码个数小于
⌈
m
/
2
⌉
−
1
\lceil m/2\rceil -1
⌈m/2⌉−1),则继续此步骤中的合并过程,直至父节点不发生下溢,便可成功返回。
图示来自 http://www.cnblogs.com/nullzx/ 。