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>
This commit is contained in:
yasinshaw
2026-02-28 23:43:36 +08:00
parent 25e6afdcb7
commit 71e3497bfd
14 changed files with 8064 additions and 0 deletions

286
questions/alg-btree.md Normal file
View File

@@ -0,0 +1,286 @@
# 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. 数据分片:大表分片减少树的大小