B树的简单实现
template<class K, size_t M>
struct BTreeNode
{
K _keys[M]; // 用于存储关键字的数组,最多容纳 M 个关键字(超额一个,为分裂提供空间)。
BTreeNode<K, M>* _subs[M + 1]; // 存储子节点的指针数组,最多 M+1 个子节点。
BTreeNode<K, M>* _parent; // 指向父节点的指针。
size_t _n; // 当前节点中实际存储的关键字数量。
// 构造函数,初始化节点的各个属性。
BTreeNode()
{
for (size_t i = 0; i < M; i++)
{
_keys[i] = K(); // 初始化关键字数组为默认值。
_subs[i] = nullptr; // 初始化子节点指针为空。
}
_subs[M] = nullptr; // 多余的子节点指针初始化为空。
_parent = nullptr; // 父节点初始化为空。
_n = 0; // 初始关键字数量为 0。
}
};
- 作用:定义 B 树节点的结构,包括关键字数组、子节点指针、父节点指针和当前节点的关键字数量。
- 意义:
- 为 B 树的基本操作(插入、删除、查找等)提供节点存储结构。
- 允许节点存储最多 M 个关键字和 M+1 个子节点。
template<class K, size_t M>
class BTree
{
typedef BTreeNode<K, M> Node; // 定义一个别名,便于后续引用节点类型。
public:
pair<Node*, int> Find(const K& key)
{
Node* cur = _root; // 从根节点开始查找。
Node* parent = nullptr; // 记录当前节点的父节点。
while (cur)
{
size_t i = 0;
// 在当前节点中逐一比较关键字
while (i < cur->_n)
{
if (key < cur->_keys[i]) // 如果目标关键字小于当前关键字,停止查找。
{
break;
}
else if (key > cur->_keys[i]) // 如果目标关键字大于当前关键字,继续查找。
{
i++;
}
else // 找到目标关键字,返回节点和索引。
{
return make_pair(cur, i);
}
}
parent = cur; // 更新父节点为当前节点。
cur = cur->_subs[i]; // 进入对应的子节点。
}
return make_pair(parent, -1); // 未找到,返回最近的父节点及无效索引。
}
void InsertKey(Node* node, const K& key, Node* child)
{
int end = node->_n - 1; // 从节点的最后一个关键字开始比较。
while (end >= 0)
{
if (key < node->_keys[end]) // 如果新关键字小于当前关键字,向右移动当前关键字和其右子树。
{
node->_keys[end + 1] = node->_keys[end];
node->_subs[end + 2] = node->_subs[end + 1];
end--;
}
else // 如果新关键字大于等于当前关键字,停止移动。
{
break;
}
}
node->_keys[end + 1] = key; // 插入新关键字。
node->_subs[end + 2] = child; // 插入对应的子节点。
if (child)
{
child->_parent = node; // 更新子节点的父节点指针。
}
node->_n++; // 更新节点的关键字数量。
}
bool Insert(const K& key)
{
if (_root == nullptr) // 如果树为空,创建根节点并插入关键字。
{
_root = new Node;
_root->_keys[0] = key;
_root->_n++;
return true;
}
// key已经存在,不允许插入
pair<Node*, int> ret = Find(key);
if (ret.second >= 0)
{
return false;
}
K newKey = key; // 保存当前要插入的关键字。
Node* child = nullptr; // 保存分裂后产生的新节点。
Node* parent = ret.first; // 从查找到的父节点开始插入。
while (1)
{
InsertKey(parent, newKey, child); // 插入关键字到节点。
if (parent->_n < M) // 如果节点未满,插入完成。
{
return true;
}
else // 如果节点满了,进行分裂。
{
size_t mid = M / 2; // 找到中间关键字的位置。
Node* brother = new Node; // 创建一个新节点作为兄弟节点。
size_t j = 0;
size_t i = mid + 1;
for (; i <= M - 1; i++) // 将中间关键字右边的部分移动到兄弟节点。
{
brother->_keys[j] = parent->_keys[i];
brother->_subs[j] = parent->_subs[i];
if (parent->_subs[i])
{
parent->_subs[i]->_parent = brother;
}
j++;
}
brother->_subs[j] = parent->_subs[i]; // 拷贝最后一个右子树。
if (parent->_subs[i])
{
parent->_subs[i]->_parent = brother;
}
brother->_n = j; // 更新兄弟节点的关键字数量。
parent->_n -= (brother->_n + 1); // 更新父节点的关键字数量。
K midkey = parent->_keys[mid]; // 提取中间关键字。
parent->_keys[mid] = K(); // 清空中间关键字。
if (parent->_parent == nullptr) // 如果分裂的是根节点,创建新根节点。
{
_root = new Node;
_root->_keys[0] = midkey;
_root->_subs[0] = parent;
_root->_subs[1] = brother;
_root->_n = 1;
parent->_parent = _root;
brother->_parent = _root;
break;
}
else // 如果分裂的不是根节点,继续向上插入。
{
newKey = midkey;
child = brother;
parent = parent->_parent;
}
}
}
return true;
}
void _Inorder(Node* root)
{
if (root == nullptr)
{
return;
}
size_t i = 0;
for (i = 0; i < root->_n; i++) // 遍历当前节点的关键字和子树。
{
_Inorder(root->_subs[i]); // 递归遍历左子树。
cout << root->_keys[i] << " "; // 打印当前关键字。
}
_Inorder(root->_subs[i]); // 遍历最后一个右子树。
}
void TestBtree()
{
int a[] = {53, 139, 75, 49, 145, 36, 101}; // 要插入的关键字数组。
BTree<int, 3> t; // 创建阶数为 3 的 B 树。
for (auto e : a)
{
t.Insert(e); // 插入每个关键字。
}
t.Inorder(); // 打印中序遍历结果。
}