B 树详解及其 Java 实现
1. 引言
B 树是一种平衡树数据结构,广泛应用于数据库和文件系统中。它是一种多路搜索树,其中每个节点可以有多个子节点和多个键。本文将详细介绍 B 树的结构、性质、操作及其 Java 实现。
2. B 树的结构与性质
2.1 B 树的定义
B 树是一种自平衡的树数据结构,具有以下性质:
- 每个节点最多有
m
个子节点(m
为 B 树的阶)。 - 除根节点和叶子节点外,每个节点至少有
⌈m/2⌉
个子节点。 - 所有叶子节点都在同一层。
- 每个节点包含
n
个键(⌈m/2⌉ - 1 <= n <= m - 1
)。 - 节点的键按升序排列,节点的子节点按键值大小分区。
2.2 B 树的应用
B 树广泛应用于数据库和文件系统中,其优点包括:
- 平衡性:所有叶子节点都在同一层,保证查找、插入和删除操作的时间复杂度为
O(log n)
。 - 高效性:适用于磁盘存储,减少磁盘 I/O 操作。
- 扩展性:支持动态增删数据,适合处理大规模数据。
3. B 树的操作
3.1 插入操作
插入操作包括以下步骤:
- 查找插入位置。
- 将键插入到相应的节点。
- 如果节点键数超过最大值,则进行分裂操作。
3.2 删除操作
删除操作包括以下步骤:
- 查找要删除的键。
- 从节点中删除键。
- 如果节点键数低于最小值,则进行合并或借用操作。
4. B 树的 Java 实现
以下是 B 树的完整 Java 实现,包括插入和删除操作。
4.1 B 树节点类
代码语言:javascript复制import java.util.ArrayList;
import java.util.List;
class BTreeNode<T extends Comparable<T>> {
int t; // 最小度数
List<T> keys; // 存储键
List<BTreeNode<T>> children; // 存储子节点
boolean leaf; // 是否为叶子节点
BTreeNode(int t, boolean leaf) {
this.t = t;
this.leaf = leaf;
this.keys = new ArrayList<>();
this.children = new ArrayList<>();
}
// 查找键
int findKey(T k) {
int idx = 0;
while (idx < keys.size() && keys.get(idx).compareTo(k) < 0) {
idx ;
}
return idx;
}
// 插入非满节点
void insertNonFull(T k) {
int i = keys.size() - 1;
if (leaf) {
keys.add(null);
while (i >= 0 && keys.get(i).compareTo(k) > 0) {
keys.set(i 1, keys.get(i));
i--;
}
keys.set(i 1, k);
} else {
while (i >= 0 && keys.get(i).compareTo(k) > 0) {
i--;
}
if (children.get(i 1).keys.size() == 2 * t - 1) {
splitChild(i 1, children.get(i 1));
if (keys.get(i 1).compareTo(k) < 0) {
i ;
}
}
children.get(i 1).insertNonFull(k);
}
}
// 分裂子节点
void splitChild(int i, BTreeNode<T> y) {
BTreeNode<T> z = new BTreeNode<>(y.t, y.leaf);
for (int j = 0; j < t - 1; j ) {
z.keys.add(y.keys.remove(t));
}
if (!y.leaf) {
for (int j = 0; j < t; j ) {
z.children.add(y.children.remove(t));
}
}
children.add(i 1, z);
keys.add(i, y.keys.remove(t - 1));
}
}
4.2 B 树类
代码语言:javascript复制public class BTree<T extends Comparable<T>> {
private BTreeNode<T> root;
private int t; // 最小度数
public BTree(int t) {
this.root = new BTreeNode<>(t, true);
this.t = t;
}
// 插入键
public void insert(T k) {
BTreeNode<T> r = root;
if (r.keys.size() == 2 * t - 1) {
BTreeNode<T> s = new BTreeNode<>(t, false);
s.children.add(r);
s.splitChild(0, r);
root = s;
s.insertNonFull(k);
} else {
r.insertNonFull(k);
}
}
// 删除键
public void delete(T k) {
if (root == null) {
return;
}
root.delete(k);
if (root.keys.isEmpty()) {
root = root.leaf ? null : root.children.get(0);
}
}
// 查找键
public boolean search(T k) {
return root == null ? false : root.search(k) != null;
}
}
4.3 B 树节点删除方法
代码语言:javascript复制class BTreeNode<T extends Comparable<T>> {
// 其他方法省略
// 删除键
void delete(T k) {
int idx = findKey(k);
if (idx < keys.size() && keys.get(idx).compareTo(k) == 0) {
if (leaf) {
keys.remove(idx);
} else {
deleteFromNonLeaf(idx);
}
} else {
if (leaf) {
return;
}
boolean flag = (idx == keys.size());
if (children.get(idx).keys.size() < t) {
fill(idx);
}
if (flag && idx > keys.size()) {
children.get(idx - 1).delete(k);
} else {
children.get(idx).delete(k);
}
}
}
// 从非叶子节点删除
void deleteFromNonLeaf(int idx) {
T k = keys.get(idx);
if (children.get(idx).keys.size() >= t) {
T pred = getPred(idx);
keys.set(idx, pred);
children.get(idx).delete(pred);
} else if (children.get(idx 1).keys.size() >= t) {
T succ = getSucc(idx);
keys.set(idx, succ);
children.get(idx 1).delete(succ);
} else {
merge(idx);
children.get(idx).delete(k);
}
}
// 获取前驱
T getPred(int idx) {
BTreeNode<T> cur = children.get(idx);
while (!cur.leaf) {
cur = cur.children.get(cur.keys.size());
}
return cur.keys.get(cur.keys.size() - 1);
}
// 获取后继
T getSucc(int idx) {
BTreeNode<T> cur = children.get(idx 1);
while (!cur.leaf) {
cur = cur.children.get(0);
}
return cur.keys.get(0);
}
// 填充子节点
void fill(int idx) {
if (idx != 0 && children.get(idx - 1).keys.size() >= t) {
borrowFromPrev(idx);
} else if (idx != keys.size() && children.get(idx 1).keys.size() >= t) {
borrowFromNext(idx);
} else {
if (idx != keys.size()) {
merge(idx);
} else {
merge(idx - 1);
}
}
}
// 从前一个子节点借
void borrowFromPrev(int idx) {
BTreeNode<T> child = children.get(idx);
BTreeNode<T> sibling = children.get(idx - 1);
for (int i = child.keys.size() - 1; i >= 0; i--) {
child.keys.set(i 1, child.keys.get(i));
}
if (!child.leaf) {
for (int i = child.children.size() - 1; i >= 0; i--) {
child.children.set(i 1, child.children.get(i));
}
}
child.keys.set(0, keys.get(idx - 1));
if (!child.leaf) {
child.children.set(0, sibling.children.remove(sibling.children.size() - 1));
}
keys.set(idx - 1, sibling.keys.remove(sibling.keys.size() - 1));
}
// 从下一个子节点借
void borrowFromNext(int idx) {
BTreeNode<T> child = children.get(idx);
BTreeNode<T> sibling = children.get(idx
1);
child.keys.add(keys.get(idx));
if (!child.leaf) {
child.children.add(sibling.children.remove(0));
}
keys.set(idx, sibling.keys.remove(0));
}
// 合并子节点
void merge(int idx) {
BTreeNode<T> child = children.get(idx);
BTreeNode<T> sibling = children.get(idx 1);
child.keys.add(keys.remove(idx));
for (int i = 0; i < sibling.keys.size(); i ) {
child.keys.add(sibling.keys.get(i));
}
if (!child.leaf) {
for (int i = 0; i < sibling.children.size(); i ) {
child.children.add(sibling.children.get(i));
}
}
children.remove(sibling);
}
// 查找键
BTreeNode<T> search(T k) {
int idx = findKey(k);
if (idx < keys.size() && keys.get(idx).compareTo(k) == 0) {
return this;
}
if (leaf) {
return null;
}
return children.get(idx).search(k);
}
}
5. 示例测试
代码语言:javascript复制public class Main {
public static void main(String[] args) {
BTree<Integer> bTree = new BTree<>(3);
bTree.insert(10);
bTree.insert(20);
bTree.insert(5);
bTree.insert(6);
bTree.insert(12);
bTree.insert(30);
bTree.insert(7);
bTree.insert(17);
System.out.println("B树创建成功。");
if (bTree.search(6)) {
System.out.println("找到键 6");
} else {
System.out.println("未找到键 6");
}
bTree.delete(6);
if (bTree.search(6)) {
System.out.println("找到键 6");
} else {
System.out.println("未找到键 6");
}
}
}
6. 运行效果
运行上述代码将演示 B 树的插入和删除操作。B 树将按顺序插入键,然后删除键 6
并进行查找验证。
7. 总结
本文详细介绍了 B 树的数据结构及其在 Java 中的实现,包括插入、删除和查找操作。通过理解和实践这些内容,可以帮助你更好地掌握 B 树的实现和应用。希望本文对你有所帮助,如有任何疑问或建议,欢迎留言讨论。