Files
interview/questions/alg-btree.md
yasinshaw 71e3497bfd feat: add comprehensive system design interview questions
- design-seckill.md: 秒杀系统设计
- design-shorturl.md: 短链接系统设计
- design-lbs.md: LBS附近的人系统设计
- design-im.md: 即时通讯系统设计
- design-feed.md: 社交信息流系统设计

Each document includes:
- Requirements analysis and data volume assessment
- Technical challenges
- System architecture design
- Database design
- Caching strategies
- Scalability considerations
- Practical project experience
- Alibaba P7 level additional points

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-28 23:43:36 +08:00

286 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# B+ 树 (B+ Tree)
## 数据结构原理
### 什么是 B+ 树?
B+ 树是一种自平衡的树数据结构,是 B 树的变体,专门为磁盘存储和索引设计。它在数据库系统和文件系统中广泛应用,特别适合磁盘等外部存储设备。
### B+ 树的特点
1. **多路搜索树**:每个节点可以有多个子节点
2. **有序结构**:所有键值按顺序存储
3. **平衡性**:从根到任何叶子的路径长度相同
4. **叶节点链表**:所有叶子节点通过指针连接成有序链表
### B+ 树与 B 树的区别
| 特性 | B+ 树 | B 树 |
|------|-------|------|
| 叶子节点 | 包含所有键值和指针 | 只包含键值 |
| 非叶子节点 | 只包含键值和指针,不包含数据 | 包含键值和指针,部分包含数据 |
| 查找效率 | 查找路径相同,但范围查询更高效 | 查找路径稍长 |
| 插入/删除 | 叶子节点统一操作,分布更均匀 | 数据分散在各级节点 |
| 范围查询 | 直接遍历叶子链表,高效 | 需要中序遍历整个树 |
## 图解说明
```
B+ 树结构示例m=3
[10, 20, 30]
/ | | \
[5, 8, 10] [15,18,20] [25,28,30] []
| | | |
[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]
```
### 关键概念
- **阶m**:每个节点的最大子节点数
- **键Key**:用于索引的值
- **指针Pointer**:指向子节点或数据的地址
- **叶子节点**:存储实际数据的节点
- **内部节点**:用于索引的中间节点
## Java 代码实现
```java
import java.util.ArrayList;
import java.util.List;
class BPlusTreeNode {
List<Integer> keys;
List<BPlusTreeNode> children;
BPlusTreeNode next; // 叶子节点间的指针
boolean isLeaf;
public BPlusTreeNode(boolean isLeaf) {
this.keys = new ArrayList<>();
this.children = new ArrayList<>();
this.isLeaf = isLeaf;
this.next = null;
}
}
public class BPlusTree {
private BPlusTreeNode root;
private int order; // B+树的阶
public BPlusTree(int order) {
this.root = new BPlusTreeNode(true);
this.order = order;
}
// 插入操作
public void insert(int key) {
if (root.keys.size() == order - 1) {
BPlusTreeNode newRoot = new BPlusTreeNode(false);
newRoot.children.add(root);
splitChild(newRoot, 0, root);
root = newRoot;
}
insertNonFull(root, key);
}
private void insertNonFull(BPlusTreeNode node, int key) {
if (node.isLeaf) {
int i = 0;
while (i < node.keys.size() && node.keys.get(i) < key) {
i++;
}
node.keys.add(i, key);
} else {
int i = 0;
while (i < node.keys.size() && node.keys.get(i) < key) {
i++;
}
if (node.children.get(i).keys.size() == order - 1) {
splitChild(node, i, node.children.get(i));
if (node.keys.get(i) < key) {
i++;
}
}
insertNonFull(node.children.get(i), key);
}
}
private void splitChild(BPlusTreeNode parent, int index, BPlusTreeNode fullNode) {
BPlusTreeNode newNode = new BPlusTreeNode(fullNode.isLeaf);
// 移动后半部分键
int mid = fullNode.keys.size() / 2;
for (int i = mid + (fullNode.isLeaf ? 0 : 1); i < fullNode.keys.size(); i++) {
newNode.keys.add(fullNode.keys.get(i));
}
fullNode.keys.subList(mid + (fullNode.isLeaf ? 0 : 1), fullNode.keys.size()).clear();
// 移动子节点(如果是内部节点)
if (!fullNode.isLeaf) {
for (int i = mid + 1; i < fullNode.children.size(); i++) {
newNode.children.add(fullNode.children.get(i));
}
fullNode.children.subList(mid + 1, fullNode.children.size()).clear();
}
// 如果是叶子节点,维护链表
if (fullNode.isLeaf) {
newNode.next = fullNode.next;
fullNode.next = newNode;
}
// 插入到父节点
parent.children.add(index + 1, newNode);
parent.keys.add(index, fullNode.keys.get(mid));
}
// 查找操作
public boolean search(int key) {
return search(root, key);
}
private boolean search(BPlusTreeNode node, int key) {
int i = 0;
while (i < node.keys.size() && node.keys.get(i) < key) {
i++;
}
if (i < node.keys.size() && node.keys.get(i) == key) {
return true;
}
if (node.isLeaf) {
return false;
} else {
return search(node.children.get(i), key);
}
}
// 范围查询
public List<Integer> rangeSearch(int start, int end) {
List<Integer> result = new ArrayList<>();
rangeSearch(root, start, end, result);
return result;
}
private void rangeSearch(BPlusTreeNode node, int start, int end, List<Integer> result) {
if (node.isLeaf) {
for (int key : node.keys) {
if (key >= start && key <= end) {
result.add(key);
}
}
} else {
int i = 0;
while (i < node.keys.size() && node.keys.get(i) < start) {
i++;
}
rangeSearch(node.children.get(i), start, end, result);
}
}
// 删除操作
public void delete(int key) {
delete(root, key);
}
private void delete(BPlusTreeNode node, int key) {
// 实现删除逻辑(简化版)
// 实际实现需要处理合并、重新平衡等复杂逻辑
}
}
```
## 时间复杂度分析
### 操作时间复杂度
| 操作 | 时间复杂度 | 说明 |
|------|------------|------|
| 查找 | O(log n) | 树高为 O(log n),每层需要比较 O(m) 次 |
| 插入 | O(log n) | 查找插入位置 + 可能的分裂操作 |
| 删除 | O(log n) | 查找删除位置 + 可能的合并操作 |
| 范围查询 | O(log n + k) | k 是结果集大小 |
### 空间复杂度
- O(n) - 需要存储 n 个元素
- 每个节点存储约 m/2 到 m 个元素
## 实际应用场景
### 1. 数据库索引
- **MySQL InnoDB**:聚簇索引使用 B+ 树
- **PostgreSQL**:标准索引使用 B+ 树
- **优点**:范围查询高效,磁盘 I/O 次数少
### 2. 文件系统
- **NTFS**:主文件表使用 B+ 树结构
- **ext4**:目录索引使用 B+ 树
- **优点**:文件查找效率高,支持大文件系统
### 3. 内存数据库
- **Redis**有序集合Sorted Set使用类似 B+ 树的结构
- **LevelDB**:底层存储引擎使用 B+ 树变种
- **优点**:内存访问速度更快,但结构保持高效
### 4. 文件搜索
- **全文搜索引擎**:倒排索引使用 B+ 树
- **文件系统搜索**:快速定位文件位置
- **优点**:前缀查询和范围查询高效
## 与其他数据结构的对比
| 数据结构 | 查找时间 | 插入时间 | 删除时间 | 适用场景 |
|----------|----------|----------|----------|----------|
| B+ 树 | O(log n) | O(log n) | O(log n) | 磁盘存储、数据库索引 |
| AVL 树 | O(log n) | O(log n) | O(log n) | 内存存储、需要平衡 |
| 红黑树 | O(log n) | O(log n) | O(log n) | 内存存储、平衡性较好 |
| 哈希表 | O(1) | O(1) | O(1) | 精确查找、内存存储 |
| 二叉搜索树 | O(log n) ~ O(n) | O(log n) ~ O(n) | O(log n) ~ O(n) | 排序数据、简单应用 |
### 选择 B+ 树的原因
1. **磁盘友好**:减少磁盘 I/O 次数
2. **范围查询高效**:叶子节点链表支持快速范围扫描
3. **局部性原理**:每次读取整个页面,充分利用磁盘预读
4. **稳定性能**:即使树不平衡,性能下降缓慢
5. **批量操作**:适合批量插入和删除
### 不同场景的最佳选择
- **内存数据**AVL 树或红黑树
- **磁盘数据**B+ 树
- **精确查找**:哈希表
- **范围查询**B+ 树
- **频繁插入删除**B+ 树或红黑树
## 常见面试问题
### Q1: 为什么数据库索引使用 B+ 树而不是 B 树?
**答**
1. 范围查询更高效B+ 树的叶子节点形成链表,范围查询只需遍历叶子节点
2. 磁盘利用率高:非叶子节点不存储数据,可以存储更多索引键
3. 查找稳定:无论查找什么数据,都到达叶子节点,树高相同
4. 预读效率高:每次读取一个完整的页面
### Q2: B+ 树的阶数如何选择?
**答**
阶数取决于:
- 磁盘块大小通常等于操作系统页面大小4KB
- 键值大小:整数 vs 字符串
- 指针大小64位系统 8 字节指针
- 缓存大小:内存缓存页面数量
### Q3: B+ 树在什么情况下性能下降?
**答**
1. 树太高:查找路径变长
2. 叶子节点过多:范围查询变慢
3. 内存不足:频繁磁盘 I/O
4. 数据分布不均:导致树不平衡
### Q4: 如何优化 B+ 树的性能?
**答**
1. 选择合适的阶数:平衡磁盘 I/O 和内存使用
2. 使用缓存:缓存热点数据
3. 预加载:提前加载可能访问的节点
4. 批量操作:合并多次插入/删除操作
5. 数据分片:大表分片减少树的大小