目录
数据结构之B树与B+树
B树
B树是一种自平衡的树数据结构,广泛应用于数据库和文件系统中。它的主要特点是能够保持数据有序,并允许高效的插入、删除和查找操作。B树的每个节点可以包含多个键和子节点,从而减少了树的高度,提高了操作效率。
B树的基本概念
- 节点:B树的每个节点可以包含多个键和子节点。节点分为内部节点和叶子节点。
- 度数:B树的度数(或称为最小度数)是一个重要参数,表示每个节点至少包含的子节点数。通常用t表示。
- 键的数量:每个节点包含的键的数量在t-1到2t-1之间。
- 子节点的数量:每个节点包含的子节点的数量在t到2t之间。
B树的性质
- 根节点:根节点至少有两个子节点,除非它是叶子节点。
- 键的排序:每个节点的键按升序排列。
- 子节点的数量:每个内部节点的子节点数量比其键的数量多1。
- 平衡性:所有叶子节点都在同一层,保证了树的平衡性。
B树的操作
- 查找:从根节点开始,根据键的大小逐层查找,直到找到目标键或到达叶子节点。
- 插入:在叶子节点插入新键,如果节点已满,则需要分裂节点并将中间键上移。
- 删除:删除键时需要考虑多种情况,包括从叶子节点删除、从内部节点删除以及节点合并等。
示例代码
以下是一个简单的B树实现示例,展示了B树的插入和查找操作:
#include <iostream>
#include <vector>
#include <algorithm>
// B 树节点类
class BTreeNode {
public:
std::vector<int> keys;
std::vector<BTreeNode*> children;
bool isLeaf;
BTreeNode(bool leaf);
void insertNonFull(int key);
void splitChild(int i, BTreeNode* y);
void traverse();
BTreeNode* search(int key);
private:
int t; // 最小度数
friend class BTree;
};
// B 树类
class BTree {
public:
BTree(int _t) : t(_t) {
root = new BTreeNode(true);
root->t = t;
}
void traverse() {
if (root != nullptr) root->traverse();
}
BTreeNode* search(int key) {
return (root == nullptr) ? nullptr : root->search(key);
}
void insert(int key);
private:
BTreeNode* root;
int t; // 最小度数
};
BTreeNode::BTreeNode(bool leaf) : isLeaf(leaf) {}
void BTreeNode::traverse() {
int i;
for (i = 0; i < keys.size(); i++) {
if (!isLeaf) children[i]->traverse();
std::cout << " " << keys[i];
}
if (!isLeaf) children[i]->traverse();
}
BTreeNode* BTreeNode::search(int key) {
int i = 0;
while (i < keys.size() && key > keys[i]) i++;
if (keys[i] == key) return this;
if (isLeaf) return nullptr;
return children[i]->search(key);
}
void BTree::insert(int key) {
if (root->keys.size() == 2 * t - 1) {
BTreeNode* s = new BTreeNode(false);
s->children.push_back(root);
s->splitChild(0, root);
int i = 0;
if (s->keys[0] < key) i++;
s->children[i]->insertNonFull(key);
root = s;
}
else {
root->insertNonFull(key);
}
}
void BTreeNode::insertNonFull(int key) {
int i = keys.size() - 1;
if (isLeaf) {
keys.push_back(0);
while (i >= 0 && keys[i] > key) {
keys[i + 1] = keys[i];
i--;
}
keys[i + 1] = key;
}
else {
while (i >= 0 && keys[i] > key) i--;
if (children[i + 1]->keys.size() == 2 * t - 1) {
splitChild(i + 1, children[i + 1]);
if (keys[i + 1] < key) i++;
}
children[i + 1]->insertNonFull(key);
}
}
void BTreeNode::splitChild(int i, BTreeNode* y) {
BTreeNode* z = new BTreeNode(y->isLeaf);
z->t = y->t;
for (int j = 0; j < t - 1; j++) z->keys.push_back(y->keys[j + t]);
if (!y->isLeaf) {
for (int j = 0; j < t; j++) z->children.push_back(y->children[j + t]);
}
if (t > 1) // 防止 t = 1 时 y->keys.resize(t - 1) 报错 (t - 1 = 0
{
y->keys.resize(t - 1);
}
y->children.resize(t);
children.insert(children.begin() + i + 1, z);
keys.insert(keys.begin() + i, y->keys[t - 1]);
}
int main() {
BTree t(3); // 创建一个最小度数为 3 的 B 树
t.insert(10);
t.insert(20);
t.insert(5);
t.insert(6);
t.insert(12);
t.insert(30);
t.insert(7);
t.insert(17);
std::cout << "遍历 B 树: ";
t.traverse();
std::cout << std::endl;
int key = 6;
(t.search(key) != nullptr) ? std::cout << key << " 存在\n" : std::cout << key << " 不存在\n";
key = 15;
(t.search(key) != nullptr) ? std::cout << key << " 存在\n" : std::cout << key << " 不存在\n";
return 0;
}
这个示例展示了如何创建一个最小度数为3的B树,并进行插入和查找操作。你可以在Visual Studio中运行这段代码,观察B树的行为。
B+树
B+树是B树的一种变体,广泛应用于数据库和文件系统中。与B树相比,B+树在查找、插入和删除操作上有一些不同的特点和优势。
B+树的基本概念
-
节点:B+树的每个节点可以包含多个键和子节点。节点分为内部节点和叶子节点。
-
度数:B+树的度数(或称为最小度数)是一个重要参数,表示每个节点至少包含的子节点数。通常用t表示。
-
键的数量:每个内部节点包含的键的数量在t-1到2t-1之间。
-
子节点的数量:每个内部节点包含的子节点的数量在t到2t之间。
-
叶子节点:所有的键和值都存储在叶子节点中,叶子节点通过指针相互连接,形成一个链表。
B+树的性质
-
根节点:根节点至少有两个子节点,除非它是叶子节点。
-
键的排序:每个节点的键按升序排列。
-
子节点的数量:每个内部节点的子节点数量比其键的数量多1。
-
平衡性:所有叶子节点都在同一层,保证了树的平衡性。
-
叶子节点链表:所有叶子节点通过指针相互连接,便于范围查询。
B+树的操作
-
查找:从根节点开始,根据键的大小逐层查找,直到找到目标键或到达叶子节点。
-
插入:在叶子节点插入新键,如果节点已满,则需要分裂节点并将中间键上移。
-
删除:删除键时需要考虑多种情况,包括从叶子节点删除、从内部节点删除以及节点合并等。
示例代码
以下是一个简单的B+树实现示例,展示了B+树的插入和查找操作:
#include <iostream>
#include <vector>
#include <algorithm>
// B+ 树节点类
class BPlusTreeNode {
public:
std::vector<int> keys;
std::vector<BPlusTreeNode*> children;
BPlusTreeNode* next; // 指向下一个叶子节点
bool isLeaf;
BPlusTreeNode(bool leaf);
void insertNonFull(int key);
void splitChild(int i, BPlusTreeNode* y);
void traverse();
BPlusTreeNode* search(int key);
private:
int t; // 最小度数
friend class BPlusTree;
};
// B+ 树类
class BPlusTree {
public:
BPlusTree(int _t) : t(_t) {
root = new BPlusTreeNode(true);
root->t = t;
}
void traverse() {
if (root != nullptr) root->traverse();
}
BPlusTreeNode* search(int key) {
return (root == nullptr) ? nullptr : root->search(key);
}
void insert(int key);
private:
BPlusTreeNode* root;
int t; // 最小度数
};
BPlusTreeNode::BPlusTreeNode(bool leaf) : isLeaf(leaf), next(nullptr) {}
void BPlusTreeNode::traverse() {
int i;
for (i = 0; i < keys.size(); i++) {
if (!isLeaf) children[i]->traverse();
std::cout << " " << keys[i];
}
if (!isLeaf) children[i]->traverse();
}
BPlusTreeNode* BPlusTreeNode::search(int key) {
int i = 0;
while (i < keys.size() && key > keys[i]) i++;
if (keys[i] == key) return this;
if (isLeaf) return nullptr;
return children[i]->search(key);
}
void BPlusTree::insert(int key) {
if (root->keys.size() == 2 * t - 1) {
BPlusTreeNode* s = new BPlusTreeNode(false);
s->children.push_back(root);
s->splitChild(0, root);
int i = 0;
if (s->keys[0] < key) i++;
s->children[i]->insertNonFull(key);
root = s;
}
else {
root->insertNonFull(key);
}
}
void BPlusTreeNode::insertNonFull(int key) {
int i = keys.size() - 1;
if (isLeaf) {
keys.push_back(0);
while (i >= 0 && keys[i] > key) {
keys[i + 1] = keys[i];
i--;
}
keys[i + 1] = key;
}
else {
while (i >= 0 && keys[i] > key) i--;
if (children[i + 1]->keys.size() == 2 * t - 1) {
splitChild(i + 1, children[i + 1]);
if (keys[i + 1] < key) i++;
}
children[i + 1]->insertNonFull(key);
}
}
void BPlusTreeNode::splitChild(int i, BPlusTreeNode* y) {
BPlusTreeNode* z = new BPlusTreeNode(y->isLeaf);
z->t = y->t;
for (int j = 0; j < t - 1; j++) z->keys.push_back(y->keys[j + t]);
if (!y->isLeaf) {
for (int j = 0; j < t; j++) z->children.push_back(y->children[j + t]);
}
if (t > 1) // 防止 t = 1 时 y->keys.resize(t - 1) 报错 (t - 1 = 0
{
y->keys.resize(t - 1);
}
y->children.resize(t);
children.insert(children.begin() + i + 1, z);
keys.insert(keys.begin() + i, y->keys[t - 1]);
if (y->isLeaf) {
z->next = y->next;
y->next = z;
}
}
int main() {
BPlusTree t(3); // 创建一个最小度数为 3 的 B+ 树
t.insert(10);
t.insert(20);
t.insert(5);
t.insert(6);
t.insert(12);
t.insert(30);
t.insert(7);
t.insert(17);
std::cout << "遍历 B+ 树: ";
t.traverse();
std::cout << std::endl;
int key = 6;
(t.search(key) != nullptr) ? std::cout << key << " 存在\n" : std::cout << key << " 不存在\n";
key = 15;
(t.search(key) != nullptr) ? std::cout << key << " 存在\n" : std::cout << key << " 不存在\n";
return 0;
}
这个示例展示了如何创建一个最小度数为3的B+树,并进行插入和查找操作。你可以在Visual Studio中运行这段代码,观察B+树的行为。
B树和B+的差别
B树和B+树都是自平衡的树数据结构,广泛应用于数据库和文件系统中。尽管它们有许多相似之处,但也有一些关键的区别。以下是它们之间的主要差异:
- 叶子节点
- B树:键和值都可以存储在内部节点和叶子节点中。
- B+树:所有的键和值都存储在叶子节点中,内部节点只存储键。
- 内部节点
- B树:内部节点包含键和值,并且可以直接用于查找操作。
- B+树:内部节点只包含键,不包含值。查找操作最终会到达叶子节点。
- 叶子节点链表
- B树:叶子节点之间没有链表结构。
- B+树:所有叶子节点通过指针相互连接,形成一个链表,便于范围查询。
- 查找效率
- B树:查找操作可能会在内部节点找到目标键。
- B+树:查找操作总是到达叶子节点,查找路径更长,但叶子节点链表使得范围查询更高效。
- 空间利用率
- B树:由于键和值都存储在内部节点和叶子节点中,空间利用率较低。
- B+树:由于内部节点只存储键,空间利用率较高。
- 范围查询
- B树:范围查询需要在树中进行多次查找。
- B+树:由于叶子节点链表的存在,范围查询非常高效,只需在叶子节点链表中遍历即可。