一、B 树和 B+ 树的基本概念
B 树和 B+ 树是两种重要的平衡多路搜索树,特别适合在磁盘等外存设备上组织和存储数据。它们通过增加节点的分支因子,减少树的高度,从而减少磁盘 I/O 操作,提高数据访问效率。
B 树 (B-Tree)
B 树是一种自平衡的多路搜索树,每个节点可以有多个子节点。
主要特点
- 所有叶子节点在同一层
- 每个节点可以包含多个键值和子节点
- 节点的键值按升序排列
- 除根节点外,每个节点至少有 m/2 个子节点 (m 为树的阶数)
- 每个节点最多有 m 个子节点
B 树的结构
class BTreeNode {
constructor(isLeaf = false) {
this.isLeaf = isLeaf;
this.keys = [];
this.children = [];
}
}
class BTree {
constructor(minDegree) {
this.root = new BTreeNode(true);
this.minDegree = minDegree; // 最小度,决定节点的最小键数和子节点数
}
// 搜索键
search(key, node = this.root) {
let i = 0;
while (i < node.keys.length && key > node.keys[i]) {
i++;
}
if (i < node.keys.length && key === node.keys[i]) {
return node;
}
if (node.isLeaf) {
return null;
}
return this.search(key, node.children[i]);
}
// 插入键
insert(key) {
const root = this.root;
if (root.keys.length === (2 * this.minDegree - 1)) {
const newRoot = new BTreeNode();
this.root = newRoot;
newRoot.children.push(root);
this.splitChild(newRoot, 0);
this.insertNonFull(newRoot, key);
} else {
this.insertNonFull(root, key);
}
}
// 在非满节点插入键
insertNonFull(node, key) {
let i = node.keys.length - 1;
if (node.isLeaf) {
node.keys.push(null);
while (i >= 0 && key < node.keys[i]) {
node.keys[i + 1] = node.keys[i];
i--;
}
node.keys[i + 1] = key;
} else {
while (i >= 0 && key < node.keys[i]) {
i--;
}
i++;
if (node.children[i].keys.length === (2 * this.minDegree - 1)) {
this.splitChild(node, i);
if (key > node.keys[i]) {
i++;
}
}
this.insertNonFull(node.children[i], key);
}
}
// 分裂子节点
splitChild(parentNode, index) {
const minDegree = this.minDegree;
const childNode = parentNode.children[index];
const newNode = new BTreeNode(childNode.isLeaf);
parentNode.children.splice(index + 1, 0, newNode);
parentNode.keys.splice(index, 0, childNode.keys[minDegree - 1]);
newNode.keys = childNode.keys.slice(minDegree);
childNode.keys = childNode.keys.slice(0, minDegree - 1);
if (!childNode.isLeaf) {
newNode.children = childNode.children.slice(minDegree);
childNode.children = childNode.children.slice(0, minDegree);
}
}
// 打印B树
printTree(node = this.root, level = 0) {
let indent = ' '.repeat(level);
console.log(indent + 'Level ' + level + ': ' + node.keys.join(', '));
if (!node.isLeaf) {
for (let i = 0; i < node.children.length; i++) {
this.printTree(node.children[i], level + 1);
}
}
}
}
// 使用示例
const bTree = new BTree(3); // 创建一个最小度为3的B树
bTree.insert(10);
bTree.insert(20);
bTree.insert(5);
bTree.insert(6);
bTree.insert(12);
bTree.insert(30);
bTree.insert(7);
bTree.insert(17);
console.log("B树结构:");
bTree.printTree();
B+ 树 (B+Tree)
B+ 树是 B 树的一种变体,它在数据库和文件系统中被广泛使用。
主要特点
- 所有数据都存储在叶子节点,非叶子节点只存储索引信息
- 叶子节点之间通过指针相连,形成有序链表
- 叶子节点包含所有键值和对应的数据记录
- 非叶子节点的键值是其子节点中最大键值的副本
- 与 B 树相比,B+ 树的查询效率更加稳定
B+ 树的结构
class BPlusTreeNode {
constructor(isLeaf = false) {
this.isLeaf = isLeaf;
this.keys = [];
this.children = [];
this.next = null; // 用于叶子节点之间的链接
}
}
class BPlusTree {
constructor(minDegree) {
this.root = new BPlusTreeNode(true);
this.minDegree = minDegree;
}
// 搜索键
search(key) {
let node = this.root;
while (!node.isLeaf) {
let i = 0;
while (i < node.keys.length && key > node.keys[i]) {
i++;
}
node = node.children[i];
}
// 在叶子节点中查找
for (let i = 0; i < node.keys.length; i++) {
if (node.keys[i] === key) {
return true;
}
}
return false;
}
// 插入键
insert(key) {
const root = this.root;
if (root.keys.length === (2 * this.minDegree - 1)) {
const newRoot = new BPlusTreeNode();
this.root = newRoot;
newRoot.children.push(root);
this.splitChild(newRoot, 0);
this.insertNonFull(newRoot, key);
} else {
this.insertNonFull(root, key);
}
}
// 在非满节点插入键
insertNonFull(node, key) {
let i = node.keys.length - 1;
if (node.isLeaf) {
node.keys.push(null);
while (i >= 0 && key < node.keys[i]) {
node.keys[i + 1] = node.keys[i];
i--;
}
node.keys[i + 1] = key;
} else {
while (i >= 0 && key < node.keys[i]) {
i--;
}
i++;
if (node.children[i].keys.length === (2 * this.minDegree - 1)) {
this.splitChild(node, i);
if (key > node.keys[i]) {
i++;
}
}
this.insertNonFull(node.children[i], key);
}
}
// 分裂子节点
splitChild(parentNode, index) {
const minDegree = this.minDegree;
const childNode = parentNode.children[index];
const newNode = new BPlusTreeNode(childNode.isLeaf);
parentNode.children.splice(index + 1, 0, newNode);
parentNode.keys.splice(index, 0, childNode.keys[minDegree - 1]);
newNode.keys = childNode.keys.slice(minDegree);
childNode.keys = childNode.keys.slice(0, minDegree - 1);
if (!childNode.isLeaf) {
newNode.children = childNode.children.slice(minDegree);
childNode.children = childNode.children.slice(0, minDegree);
} else {
// 处理叶子节点之间的链接
newNode.next = childNode.next;
childNode.next = newNode;
}
}
// 范围查询
rangeQuery(start, end) {
const result = [];
let node = this.root;
// 找到起始键所在的叶子节点
while (!node.isLeaf) {
let i = 0;
while (i < node.keys.length && start > node.keys[i]) {
i++;
}
node = node.children[i];
}
// 遍历叶子节点链表,收集范围内的键
while (node) {
for (let key of node.keys) {
if (key >= start && key <= end) {
result.push(key);
}
if (key > end) {
return result;
}
}
node = node.next;
}
return result;
}
// 打印B+树
printTree() {
let node = this.root;
let level = 0;
while (!node.isLeaf) {
console.log(`Level ${level}: ${node.keys.join(', ')}`);
node = node.children[0];
level++;
}
// 打印叶子节点
console.log(`Level ${level} (Leaf):`);
let leafNode = node;
let leafLevel = [];
while (leafNode) {
leafLevel.push(...leafNode.keys);
leafNode = leafNode.next;
}
console.log(leafLevel.join(', '));
}
}
// 使用示例
const bPlusTree = new BPlusTree(3);
bPlusTree.insert(10);
bPlusTree.insert(20);
bPlusTree.insert(5);
bPlusTree.insert(6);
bPlusTree.insert(12);
bPlusTree.insert(30);
bPlusTree.insert(7);
bPlusTree.insert(17);
console.log("B+树结构:");
bPlusTree.printTree();
console.log("范围查询 (5-15):", bPlusTree.rangeQuery(5, 15));
B 树和 B+ 树的主要区别
1. 数据存储位置:
- B 树:键值和数据都存储在所有节点中
- B+ 树:只有叶子节点存储数据,非叶子节点只存储索引
2. 搜索效率:
- B 树:搜索可能在任何节点结束
- B+ 树:搜索必须到达叶子节点才能结束,效率更稳定
3. 范围查询:
- B 树:需要中序遍历整个树
- B+ 树:通过叶子节点的链表可以高效地进行范围查询
4. 空间利用:
- B 树:每个节点都存储数据,空间利用率较高
- B+ 树:非叶子节点不存储数据,空间利用率相对较低
5. 应用场景:
- B 树:适用于随机访问较多的场景
- B+ 树:适用于范围查询较多的场景,如数据库索引
B 树和 B+ 树的应用
1. 数据库系统:
- MySQL、Oracle 等关系型数据库的索引结构通常使用 B+ 树
- MongoDB 等 NoSQL 数据库也使用 B 树或 B+ 树作为索引结构
2. 文件系统:
- NTFS、ext4 等文件系统使用 B+ 树来组织文件索引
- B+ 树的结构非常适合文件系统中的范围查询
3. 内存数据库:
- Redis 等内存数据库在需要持久化时也会使用 B 树或 B + 树
4. 搜索引擎:
- 搜索引擎的索引结构也会使用 B 树或 B+ 树来提高查询效率
B 树和 B+ 树是现代计算机系统中非常重要的数据结构,它们通过平衡多路搜索的特性,有效地减少了磁盘 I/O 操作,提高了数据访问效率,是数据库和文件系统等应用的核心组件。