学习一下B树和B+树
学习一下B树和B+树
前言
抱歉这里鸽了5天,笔者最近忙于秋招找工作,连一些项目都被暂时的搁置了。这里笔者再干一个需要手搓B+树的题目中遇到了,虽然知道这个题大致需要使用B+树解决问题,但是发现自己好像对B/B+树实际上不够熟悉,这里笔者整理一下笔记放到下面,供各位参考和批评。
可以在哪里看到他们呢?
在数据库和文件系统中,B树(Balanced Tree)和B+树是核心的数据结构。它们通过多路平衡搜索,提供高效的查找、插入和范围查询能力。换而言之,在比较注重关系类型的查询的时候,我们很容易联想到使用B/B+树来进行关系的构建。
以文件系统为例子,我们可以非常自然的想到,我们的目录节点是一个非叶子节点(我指代的是下面还有孩子节点,对于二叉树,这个个数非常固定的就是2,这个无可厚非),下面存储若干的子节点(也不一定是叶子节点),所以我们的节点立马也就变成了这样的结构:
struct Node {bool isLeaf;std::vector<T> keys; // T是一个给定的类型 std::vector<Node*> children;Node* next;Node(bool leaf) : isLeaf(leaf), next(nullptr) {}};
因此,在一个存在层次(使用目录的)的文件系统中,我们实际上就是查找由Node构成的B(+)树的结构。
[charliechen@Charliechen CChatRoom]$ tree .
.
├── client
│ ├── client_loader.cpp
│ └── CMakeLists.txt
├── CMakeLists.txt
├── library
│ ├── CMakeLists.txt
│ ├── simple_logger
│ │ ├── simple_logger.cpp
│ │ └── simple_logger.h
│ └── sockets_operation
│ ├── passive_client_socket.hpp
│ ├── socket_error.hpp
│ ├── sockets_common.hpp
│ ├── sockets_driver_impl.h
│ ├── sockets_driver_impl_linux.cpp
│ ├── sockets_driver_impl_linux.h
│ ├── sockets_operation.cpp
│ └── sockets_operation.h
├── server
│ └── CMakeLists.txt
└── test├── CMakeLists.txt├── test_client.cpp└── test_sockets.cpp
B树(B+树)这样的数据结构。下面我们来看看概念。
B树(Balanced Tree)
B树是一种多路平衡搜索树,用于在磁盘或内存中高效存储大量数据。它保证树的高度为对数级别,因此查找、插入、删除操作的时间复杂度均为 O(logn)O(\log n)O(logn)。
我们要求,一个B树的特点必须是:
- 多路节点:一个节点可以有多个子节点,而不仅限于二叉。
- 关键字有序:节点内关键字按升序排列。
- 所有叶子节点在同一层:保证平衡。
- 节点关键字和子树数约束:
- 阶数 M 的 B 树,每个节点关键字数在 [⌈M/2⌉−1,M−1][⌈M/2⌉-1, M-1][⌈M/2⌉−1,M−1]。这样的约束是为了保证
- 子树数在 [⌈M/2⌉,M][⌈M/2⌉, M][⌈M/2⌉,M]。
- 查找过程:
- 在节点关键字中查找目标值或确定进入哪颗子树。
- 递归查找,直到叶子节点或找到目标。
几个经典的操作
- 查找:类似二分查找,沿着多路节点向下查找。
- 插入:
- 找到合适叶子节点。
- 插入关键字。
- 节点满时分裂,可能递归向上分裂。
- 删除:
- 删除关键字。
- 节点关键字数不足时,向兄弟节点借或合并。
B+树
特点
B+树实际上就是B树的一点改进,由于非叶子节点不存储信息,直接查叶子节点就好了。
- 非叶子节点只存索引:不存储具体数据,节省空间。
- 叶子节点存储所有数据:并按照关键字顺序形成链表。
- 查找路径固定:所有查找操作最终都在叶子节点完成。
- 范围查询高效:叶子链表支持顺序扫描,方便扫描范围数据。
- 多路平衡:和B树类似,保持高度平衡。
特性 | B树 | B+树 |
---|---|---|
数据存储位置 | 非叶子节点 + 叶子节点 | 仅叶子节点 |
非叶子节点 | 存索引和数据 | 仅存索引 |
叶子节点 | 无链表 | 叶子链表支持顺序遍历 |
查找路径 | 命中可提前结束 | 必须到叶子节点 |
范围查询 | 中序遍历 | 叶子链表顺序扫描 |
磁盘IO | 查找路径短 | 内部节点更小,扇出大,总体IO少 |
应用场景 | 小型索引 | 数据库索引、文件系统 |
一个潜在的模板代码
#include <iostream>
#include <vector>
#include <algorithm>template <typename T, int M = 4>
class BPlusTree {struct Node {bool isLeaf;std::vector<T> keys;std::vector<Node*> children;Node* next;Node(bool leaf) : isLeaf(leaf), next(nullptr) {}};Node* root;std::pair<T, Node*> split(Node* node) {int mid = node->keys.size() / 2;Node* right = new Node(node->isLeaf);right->keys.assign(node->keys.begin() + mid, node->keys.end());node->keys.resize(mid);if (!node->isLeaf) {right->children.assign(node->children.begin() + mid + 1, node->children.end());node->children.resize(mid + 1);}if (node->isLeaf) {right->next = node->next;node->next = right;}return { right->keys[0], right };}std::pair<T, Node*> insert(Node* node, const T& key) {if (node->isLeaf) {auto it = std::upper_bound(node->keys.begin(), node->keys.end(), key);node->keys.insert(it, key);} else {int i = 0;while (i < node->keys.size() && key >= node->keys[i]) i++;auto ret = insert(node->children[i], key);if (ret.second) {node->keys.insert(node->keys.begin() + i, ret.first);node->children.insert(node->children.begin() + i + 1, ret.second);}}if (node->keys.size() >= M) return split(node);return { T(), nullptr };}public:BPlusTree() { root = new Node(true); }void insert(const T& key) {auto ret = insert(root, key);if (ret.second) {Node* newRoot = new Node(false);newRoot->keys.push_back(ret.first);newRoot->children.push_back(root);newRoot->children.push_back(ret.second);root = newRoot;}}bool search(const T& key) {Node* cur = root;while (!cur->isLeaf) {int i = 0;while (i < cur->keys.size() && key >= cur->keys[i]) i++;cur = cur->children[i];}return std::find(cur->keys.begin(), cur->keys.end(), key) != cur->keys.end();}void printAll() {Node* cur = root;while (!cur->isLeaf) cur = cur->children[0];while (cur) {for (auto k : cur->keys) std::cout << k << " ";cur = cur->next;}std::cout << std::endl;}
};int main() {BPlusTree<int,4> tree;for(int x: {10,20,5,15,25,30,1}) tree.insert(x);std::cout << "查找15: " << tree.search(15) << std::endl;std::cout << "查找7: " << tree.search(7) << std::endl;std::cout << "顺序遍历: ";tree.printAll();
}