定义
一个 m 阶的B+树是一个有以下属性的树:
- 每一个节点最多有 m 个子节点
- 每一个非叶子节点(除根节点)最少有 ⌈m/2⌉ 个子节点
- 如果根节点不是叶子节点,那么它至少有两个子节点
- 有 k 个子节点的非叶子节点拥有 k − 1 个键
- 所有的叶子节点都在同一层
- 所有的键值对(即数据记录)都存储在叶子节点中,非叶子节点仅存储键(索引)用于路由。
- 所有的叶子节点通过指针连接成一个有序链表,便于范围查询和顺序访问。
- 非叶子节点中的键也会出现在叶子节点中,作为索引。
注:由于m=2时B+树的实际意义并不大,因此本文实现的代码仅支持m>=3的B+树
结构
B+树
template<int order,typename Key,typename Value,typename Compare=std::less<Key>>
class BPTree{
private:
struct Node{
......
};
Compare compare;
int size;
Node *root;
Node *head;
std::stack<Node*> findNodeByKey(Key key);
void adjustNodeForUpOver(Node *node,Node* parent);
void adjustNodeForDownOver(Node *node,Node* parent);
void maintainAfterInsert(std::stack<Node*>& nodePathStack);
void maintainAfterRemove(std::stack<Node*>& nodePathStack);
public:
BPTree(){
assert(order>=3);
this->size = 0;
this->root = nullptr;
this->head = nullptr;
this->compare = Compare();
}
int insert(Key key,Value value);
int remove(Key key);
void leafTraversal();
void levelOrderTraversal();
};
叶子节点维护的信息
成员名 | 类型 | 描述 |
---|---|---|
n | int | 节点数量 |
IS_LEAF | bool | 是否为叶子节点 |
keys | Key[] | 节点键值的数组,具有唯一性和可排序性 |
values | Value* | 叶子节点保存的值的数组 |
ptr | Node** | 对于叶子节点而言,其为双向链表中节点指向前后节点的指针,ptr[0]表示前一个节点,ptr[1]表示后一个节点 对与非叶子节点而言,其指向孩子节点,keys[i]的左子树是ptr[i],右子树是ptr[i+1] |
节点结构定义
struct Node{
int n;
bool IS_LEAF;
Key keys[order];
Value* values;
Node** ptr;
Node(bool isLeaf){
this->n = 0;
this->IS_LEAF = isLeaf;
if(this->IS_LEAF){
this->values = new Value[order];
this->ptr = new Node*[2];
for(int i=0;i<2;i++){
this->ptr[i] = nullptr;
}
}else{
this->values = nullptr;
this->ptr = new Node*[order+1];
for(int i=0;i<order+1;i++){
this->ptr[i] = nullptr;
}
}
}
~Node(){
if(this->isLeaf()){
delete[] this->values;
}
delete[] this->ptr;
}
// 二分查找,返回第一个大于等于该节点key的下标
inline int search(Key key,const Compare& compare) const noexcept{
// 避免因为极端情况导致的查询效果低下
if(!this->n || !compare(this->keys[0],key)) return 0;
if(this->n && compare(this->keys[this->n-1],key)) return this->n;
int i=1,j=this->n-1;
while(i<=j){
int mid = i+((j-i)>>1);
if(this->keys[mid] == key) return mid;
if(compare(this->keys[mid],key)) i = mid + 1;
else j = mid - 1;
}
return i;
}
// 判断节点是否含有key
inline bool hasKey(Key key,const Compare& compare) const noexcept{
int arg = this->search(key,compare);
return this->keys[arg] == key;
}
// 是否上溢出
inline bool isUpOver() const noexcept{
return this->n >= order;
}
// 是否下溢出
inline bool isDownOver() const noexcept{
return this->n < ((order-1)>>1);
}
//是否为叶子节点
inline bool isLeaf() const noexcept{
return this->IS_LEAF;
}
// 插入节点
inline void insert(Key key,Value value,const Compare& compare){
assert(this->isLeaf());
int arg = this->search(key,compare);
for(int i=this->n;i>arg;i--){
this->keys[i] = this->keys[i-1];
this->values[i] = this->values[i-1];
}
this->keys[arg] = key;
this->values[arg] = value;
this->n++;
}
// 插入节点及其右子树
inline void insert(Key key,Node* rightChild,const Compare& compare){
assert(!this->isLeaf());
int arg = this->search(key,compare);
for(int i=this->n;i>arg;i--){
this->keys[i] = this->keys[i-1];
this->ptr[i+1] = this->ptr[i];
}
this->keys[arg] = key;
this->ptr[arg+1] = rightChild;
this->n++;
}
// 更新节点
inline void update(Key key,Value value,const Compare& compare){
assert(this->isLeaf());
int arg = this->search(key,compare);
this->values[arg] = value;
}
// 删除节点及其右子树
inline void remove(Key key,const Compare& compare){
int arg = this->search(key,compare);
for(int i=arg;i<this->n-1;i++){
this->keys[i] = this->keys[i+1];
if(!this->isLeaf()) this->ptr[i+1] = this->ptr[i+2];
else this->values[i] = this->values[i+1];
}
if(!this->isLeaf()) this->ptr[this->n] = nullptr;
this->n--;
}
// 上溢出(n >= order)的时候调用,分裂成左右子树,自身变成左子树,返回右子树
inline Node* split(){
Node* newNode = new Node(this->isLeaf());
int mid = (order>>1);
if(this->isLeaf()){
for(int i=0,j=mid;j<this->n;i++,j++){
newNode->keys[i] = this->keys[j];
newNode->values[i] = this->values[j];
newNode->n++;
}
this->insertNextNode(newNode);
}else{
newNode->ptr[0] = this->ptr[mid+1];
this->ptr[mid+1] = nullptr;
for(int i=0,j=mid+1;j<this->n;i++,j++){
newNode->keys[i] = this->keys[j];
newNode->ptr[i+1] = this->ptr[j+1];
newNode->n++;
this->ptr[j+1] = nullptr;
}
}
this->n = mid;
return newNode;
}
// 非叶子节点下溢出(n < (order>>1))且兄弟无法借出节点时调用,和右兄弟合并
inline void merge(Key key,Node *rightSibling) {
assert(!this->isLeaf());
this->keys[this->n] = key;
this->ptr[this->n+1] = rightSibling->ptr[0];
this->n++;
for(int i=0;i<rightSibling->n;i++){
this->keys[this->n] = rightSibling->keys[i];
this->ptr[this->n+1] = rightSibling->ptr[i+1];
this->n++;
}
delete rightSibling;
}
// 叶子节点下溢出(n < (order>>1))且兄弟无法借出节点时调用,和右兄弟合并
inline void merge(Node *rightSibling){
assert(this->isLeaf());
for(int i=0;i<rightSibling->n;i++){
this->keys[this->n] = rightSibling->keys[i];
this->values[this->n] = rightSibling->values[i];
this->n++;
}
this->removeNextNode();
delete rightSibling;
}
// 双向链表中插入下一个叶子节点
inline void insertNextNode(Node *nextNode){
assert(this->isLeaf() && nextNode);
nextNode->ptr[1] = this->ptr[1];
if(this->ptr[1]) this->ptr[1]->ptr[0] = nextNode;
this->ptr[1] = nextNode;
nextNode->ptr[0] = this;
}
// 双向链表中删除下一个叶子节点
inline void removeNextNode(){
assert(this->isLeaf());
if(this->ptr[1]->ptr[1]) this->ptr[1]->ptr[1]->ptr[0] = this;
if(this->ptr[1]) this->ptr[1] = this->ptr[1]->ptr[1];
}
};
前置说明
查找含有key的节点
具体流程
B+树本身也是搜索树,可以直接按照搜索树的搜索流程进行,但是注意由于仅叶子节点才会存储数据,因此最终查找的终止节点一定是叶子节点。由于查找路径会在插入和删除时用到,因此可以直接返回保存了查找路径的栈,则栈顶即为含有key的节点,
注:调用此函数需要保证根节点存在
实现代码
/**
* @brief 查找含有key的节点(需保证根节点存在)
* @param key 键值
* @return 保存了查找路径的栈,栈顶即为含有key的节点
*/
template<int order,typename Key,typename Value,typename Compare>
std::stack<typename BPTree<order,Key,Value,Compare>::Node*> BPTree<order,Key,Value,Compare>::findNodeByKey(Key key){
std::stack<Node*> nodePathStack;
Node* node = this->root;
nodePathStack.push(node);
while(!node->isLeaf()){
int arg = node->search(key,this->compare);
if(arg<node->n && node->keys[arg]==key) arg++;
node = node->ptr[arg];
nodePathStack.push(node);
}
return nodePathStack;
}
B+树的插入
具体流程
- 首先检查根节点是否存在,若不存在则创建根节点并插入新的数据,之后将头节点设置为根节点,返回
- 查找要插入的键是否已存在,若存在,则直接更新数据并返回
- 向叶子节点直接插入数据
- 插入新的数据后可能会导致节点上溢出,因此需要对插入新数据后所有受到影响的节点(也就是查询路径上的节点)进行维护
实现代码
/**
* @brief 插入键值对
* @param key 新的键
* @param value 新的值
* @return int 0表示插入成功,1表示节点已存在,更新value
*/
template<int order,typename Key,typename Value,typename Compare>
int BPTree<order,Key,Value,Compare>::insert(Key key,Value value){
if(this->root==nullptr){
this->root = new Node(true);
this->root->insert(key,value,this->compare);
this->head = this->root;
return 0;
}
std::stack<Node*> nodePathStack = this->findNodeByKey(key);
Node *node = nodePathStack.top();
if(node->hasKey(key,this->compare)){
node->update(key,value,this->compare);
return 1;
}
node->insert(key,value,this->compare);
this->maintainAfterInsert(nodePathStack);
this->size++;
return 0;
}
B+树插入后的维护
插入后的维护
具体流程
B+树插入后的维护即是检查被维护的节点是否上溢出(保存数据的数量n==order),若未溢出,则无需维护当前和后续节点,反之若上溢出,则当前节点需要结合其父节点进行调整。注意若出现根节点上溢出,则需要创建一个新节点作为根节点,并让当前节点作为其第一个孩子进行调整父子间的调整
实现代码
/**
* @brief 插入新数据后的维护
* @param nodePathStack 保存了因为插入而受到影响的节点的栈
* @return void
*/
template<int order,typename Key,typename Value,typename Compare>
void BPTree<order,Key,Value,Compare>::maintainAfterInsert(std::stack<Node*>& nodePathStack){
Node *node,*parent;
node = nodePathStack.top();nodePathStack.pop();
while(!nodePathStack.empty()){
parent = nodePathStack.top();nodePathStack.pop();
if(!node->isUpOver()) return ;
this->adjustNodeForUpOver(node,parent);
node = parent;
}
if(!node->isUpOver()) return ;
this->root = new Node(false);
parent = this->root;
parent->ptr[0] = node;
this->adjustNodeForUpOver(node, parent);
}
父子间的调整
具体流程
将上溢出的子节点根据中间数分成不包括中间数的左右两个部分,该过程可以调用Node结构体函数split函数实现:
-
对叶子节点而言,其可以将自身分成左右两个节点,自身变为左节点,并向双向链表中插入右节点,最后返回右节点(包含中间树)
-
对非叶子节点而言:其可以将自身分成左右两个节点,自身变为左节点,并返回右节点(不包含中间树)
最后将中间数和右节点插入父节点即可
实现代码
split函数
// 上溢出(n >= order)的时候调用,分裂成左右子树,自身变成左子树,返回右子树
inline Node* split(){
Node* newNode = new Node(this->isLeaf());
int mid = (order>>1);
if(this->isLeaf()){
for(int i=0,j=mid;j<this->n;i++,j++){
newNode->keys[i] = this->keys[j];
newNode->values[i] = this->values[j];
newNode->n++;
}
this->insertNextNode(newNode);
}else{
newNode->ptr[0] = this->ptr[mid+1];
this->ptr[mid+1] = nullptr;
for(int i=0,j=mid+1;j<this->n;i++,j++){
newNode->keys[i] = this->keys[j];
newNode->ptr[i+1] = this->ptr[j+1];
newNode->n++;
this->ptr[j+1] = nullptr;
}
}
this->n = mid;
return newNode;
}
调整函数
/**
* @brief 调整上溢出节点
* @param node 上溢出节点
* @param parent 上溢出节点的父亲
* @return void
*/
template<int order,typename Key,typename Value,typename Compare>
void BPTree<order,Key,Value,Compare>::adjustNodeForUpOver(Node *node,Node* parent){
// For node As LeafNode
// parent: ... ... ... mid ...
// / =====> / |
// node: [left] mid [right] [left] mid:[right]
// For node As Node
// parent: ... ... ... mid ...
// / =====> / |
// node: [left] mid [right] [left] [right]
//
int mid = (order>>1);
Key key = node->keys[mid];
Node *rightChild = node->split();
parent->insert(key,rightChild,this->compare);
}
B+树的删除
具体流程
- 首先检查根节点是否存在,若不存在,则删除失败
- 检查被删除的键是否存在,若不存在,则删除失败
- 查找到的节点一定为叶子节点,可直接节点中删除键为key的数据
- 删除数据后可能会导致节点下溢出,因此需要对删除数据后所有受到影响的节点(也就是查询路径上的节点)进行维护
实现代码
/**
* @brief 根据键删除数据
* @param key 要被删除的数据的键
* @return int 0表示删除成功,1表示键不存在,删除失败
*/
template<int order,typename Key,typename Value,typename Compare>
int BPTree<order,Key,Value,Compare>::remove(Key key){
if(this->root == nullptr) return 1;
std::stack<Node*> nodePathStack = this->findNodeByKey(key);
Node *node =nodePathStack.top();
if(!node->hasKey(key,this->compare)) return 1;
node->remove(key,this->compare);
this->maintainAfterRemove(nodePathStack);
this->size--;
return 0;
}
B+树删除后的维护
删除后的维护
具体流程
B+树删除后的维护即是检查被维护的节点是否下溢出(保存数据的数量n<(order+1)/2-1),若未溢出,则无需维护当前和后续节点,反之若下溢出,则当前节点需要结合其父节点进行调整。注意若当前节点是根节点,且其因为删除或删除后维护而造成数据量归0,则需要判断根节点是否为叶子节点,若为叶子节点,则删除根节点和头节点,反之则更新根节点为其第一个孩子(具体为什么则这样可以对照插入的流程)
实现代码
/**
* @brief 删除数据后的维护
* @param nodePathStack 保存了因为删除而受到影响的节点的栈
* @return void
*/
template<int order,typename Key,typename Value,typename Compare>
void BPTree<order,Key,Value,Compare>::maintainAfterRemove(std::stack<Node*>& nodePathStack){
Node *node,*parent;
node = nodePathStack.top();nodePathStack.pop();
while(!nodePathStack.empty()){
parent = nodePathStack.top();nodePathStack.pop();
if(!node->isDownOver()) return ;
this->adjustNodeForDownOver(node,parent);
node = parent;
}
if(this->root->n) return ;
if(!this->root->isLeaf()) this->root = node->ptr[0];
else{
this->root = nullptr;
this->head = nullptr;
}
delete node;
}
父子间的调整
具体流程
父子间的调整需要根据子节点的类型来处理
若子节点为叶子节点,其调整过程与B树的调整有区别
case 1:左兄弟或右兄弟可以借出一个数据
若是左兄弟借出一个数据,则该数据为左兄弟的最后一个数据,将该数据插入到子节点,并从左兄弟中删除最后一个数据,同时将父键更新为子节点的第一个键;
若是右兄弟借出一个数据,则该数据为右兄弟的第一个数据,将该数据插入到子节点,并从右兄弟中删除第一个数据,同时将父键更新为右兄弟的第一个键
// 左兄弟借出一个数据
// For node As LeafNode
// parent: ... key ... ... last ...
// / \ =====> / \
// [......]:last [node] [......] last:[node]
// 右兄弟借出一个数据
// For node As LeafNode
// parent: ... key ... ... second ...
// / \ =====> / \
// [node] first:second:[......] [node]:first second:[......]
case 2:左兄弟或右兄弟都不能借出一个数据
此时子节点可以选择与其一个存在的兄弟进行合并,根据其左右顺序,可以分为合并的左节点和右节点。合并的流程是,直接与合并的右节点进行合并,合并的过程可以调用Node结构体函数merge函数,该函数接受合并的右节点作为参数进行合并,自身变成合并后的结果,最后删除父键
// 和左兄弟合并
// For node As LeafNode
// parent: ... key ... ... ...
// / \ =====> /
// [left] [node] [left]:[node]
// 和右兄弟合并
// For node As LeafNode
// parent: ... key ... ... ...
// / \ =====> /
// [node] [right] [node]:[right]
若子节点为非叶子节点,其调整过程与B树的调整完全相同
case 1:左兄弟或右兄弟可以借出一个数据
左右兄弟借出的数据不能直接给到子节点,不然会破坏搜索树的性质,此时可以将父节点中,左右子树分别为子节点和兄弟的数据下放到子节点,同时将兄弟借出的数据替换父节点下放的数据。若是左兄弟借出一个数据,则该数据为左兄弟的最后一个数据,若是右兄弟借出一个数据,则该数据为右兄弟的第一个数据
// 左兄弟借出一个数据
// For node As Node
// parent: ... key ... ... last ...
// / \ =====> / \
// [......]:last [node] [......] key:[node]
// 右兄弟借出一个数据
// For node As Node
// parent: ... key ... ... first ...
// / \ =====> / \
// [node] first:[......] [node]:key [......]
case 2:左兄弟或右兄弟都不能借出一个数据
此时子节点可以选择与其一个存在的兄弟进行合并,根据其左右顺序,可以分为合并的左节点和右节点。合并的流程是,将父节点中,左右子树分别为子节点和兄弟的数据下放到合并的左节点,之后与合并的右节点进行合并,合并的过程可以调用Node结构体函数merge函数,该函数可以接受父亲的数据和合并的右节点进行合并,自身变成合并后的结果,最后删除父键
// 和左兄弟合并
// For node As Node
// parent: ... key ... ... ...
// / \ =====> /
// [left] [node] [left]:key:[node]
// 和右兄弟合并
// For node As Node
// parent: ... key ... ... ...
// / \ =====> /
// [node] [right] [node]:key:[right]
实现代码
叶子节点的merge函数
// 叶子节点下溢出(n < (order>>1))且兄弟无法借出节点时调用,和右兄弟合并
inline void merge(Node *rightSibling){
assert(this->isLeaf());
for(int i=0;i<rightSibling->n;i++){
this->keys[this->n] = rightSibling->keys[i];
this->values[this->n] = rightSibling->values[i];
this->n++;
}
this->removeNextNode();
delete rightSibling;
}
非叶子节点的merge函数
// 非叶子节点下溢出(n < (order>>1))且兄弟无法借出节点时调用,和右兄弟合并
inline void merge(Key key,Node *rightSibling) {
assert(!this->isLeaf());
this->keys[this->n] = key;
this->ptr[this->n+1] = rightSibling->ptr[0];
this->n++;
for(int i=0;i<rightSibling->n;i++){
this->keys[this->n] = rightSibling->keys[i];
this->ptr[this->n+1] = rightSibling->ptr[i+1];
this->n++;
}
delete rightSibling;
}
调整函数
/**
* @brief 调整下溢出节点
* @param node 下溢出的节点
* @param parent 下溢出节点的父亲
* @return void
*/
template<int order,typename Key,typename Value,typename Compare>
void BPTree<order,Key,Value,Compare>::adjustNodeForDownOver(Node *node,Node* parent){
int mid = ((order-1)>>1);
int arg=-1;
if(node->n) arg = parent->search(node->keys[0],this->compare);
else while(parent->ptr[++arg]!=node);
Node* left = arg > 0 ? parent->ptr[arg-1] : nullptr;
Node* right = arg < parent->n ? parent->ptr[arg+1] : nullptr;
// case 1: 左兄弟或右兄弟可以借出一个数据
if((left && left->n > mid) || (right && right->n > mid)){
// 左兄弟借出一个数据
// For node As LeafNode
// parent: ... key ... ... last ...
// / \ =====> / \
// [......]:last [node] [......] last:[node]
// For node As Node
// parent: ... key ... ... last ...
// / \ =====> / \
// [......]:last [node] [......] key:[node]
if(left && left->n > mid){
if(node->isLeaf()){
node->insert(left->keys[left->n-1],left->values[left->n-1],this->compare);
}else{
node->insert(parent->keys[arg-1],node->ptr[0],this->compare);
node->ptr[0] = left->ptr[left->n];
}
parent->keys[arg-1] = left->keys[left->n-1];
left->remove(left->keys[left->n-1],this->compare);
}
// 右兄弟借出一个数据
// For node As LeafNode
// parent: ... key ... ... second ...
// / \ =====> / \
// [node] first:second:[......] [node]:first second:[......]
// For node As Node
// parent: ... key ... ... first ...
// / \ =====> / \
// [node] first:[......] [node]:key [......]
else if(right && right->n > mid){
if(node->isLeaf()){
node->insert(right->keys[0],right->values[0],this->compare);
right->remove(right->keys[0],this->compare);
parent->keys[arg] = right->keys[0];
}else{
node->insert(parent->keys[arg],right->ptr[0],this->compare);
right->ptr[0] = right->ptr[1];
parent->keys[arg] = right->keys[0];
right->remove(right->keys[0],this->compare);
}
}
return ;
}
// case 2: 左兄弟或右兄弟都不能借出一个数据
if(left){
// 和左兄弟合并
// For node As LeafNode
// parent: ... key ... ... ...
// / \ =====> /
// [left] [node] [left]:[node]
// For node As Node
// parent: ... key ... ... ...
// / \ =====> /
// [left] [node] [left]:key:[node]
Key key = parent->keys[arg-1];
if(left->isLeaf()) left->merge(node);
else left->merge(key,node);
parent->remove(key,this->compare);
}
else if(right){
// 和右兄弟合并
// For node As LeafNode
// parent: ... key ... ... ...
// / \ =====> /
// [node] [right] [node]:[right]
// For node As Node
// parent: ... key ... ... ...
// / \ =====> /
// [node] [right] [node]:key:[right]
Key key = parent->keys[arg];
if(node->isLeaf()) node->merge(right);
else node->merge(key,right);
parent->remove(key,this->compare);
}
}
参考资料
OIwiki B+树 https://oi-wiki.org/ds/bplus-tree
完整代码
#include <cassert>
#include <functional>
#include <stack>
#include <queue>
#include <iostream>
#include <utility>
template<int order,typename Key,typename Value,typename Compare=std::less<Key>>
class BPTree{
private:
struct Node{
int n;
bool IS_LEAF;
Key keys[order];
Value* values;
Node** ptr;
Node(bool isLeaf){
this->n = 0;
this->IS_LEAF = isLeaf;
if(this->IS_LEAF){
this->values = new Value[order];
this->ptr = new Node*[2];
for(int i=0;i<2;i++){
this->ptr[i] = nullptr;
}
}else{
this->values = nullptr;
this->ptr = new Node*[order+1];
for(int i=0;i<order+1;i++){
this->ptr[i] = nullptr;
}
}
}
~Node(){
if(this->isLeaf()){
delete[] this->values;
}
delete[] this->ptr;
}
// 二分查找,返回第一个大于等于该节点key的下标
inline int search(Key key,const Compare& compare) const noexcept{
// 避免因为极端情况导致的查询效果低下
if(!this->n || !compare(this->keys[0],key)) return 0;
if(this->n && compare(this->keys[this->n-1],key)) return this->n;
int i=1,j=this->n-1;
while(i<=j){
int mid = i+((j-i)>>1);
if(this->keys[mid] == key) return mid;
if(compare(this->keys[mid],key)) i = mid + 1;
else j = mid - 1;
}
return i;
}
// 判断节点是否含有key
inline bool hasKey(Key key,const Compare& compare) const noexcept{
int arg = this->search(key,compare);
return this->keys[arg] == key;
}
// 是否上溢出
inline bool isUpOver() const noexcept{
return this->n >= order;
}
// 是否下溢出
inline bool isDownOver() const noexcept{
return this->n < ((order-1)>>1);
}
//是否为叶子节点
inline bool isLeaf() const noexcept{
return this->IS_LEAF;
}
// 插入节点
inline void insert(Key key,Value value,const Compare& compare){
assert(this->isLeaf());
int arg = this->search(key,compare);
for(int i=this->n;i>arg;i--){
this->keys[i] = this->keys[i-1];
this->values[i] = this->values[i-1];
}
this->keys[arg] = key;
this->values[arg] = value;
this->n++;
}
// 插入节点及其右子树
inline void insert(Key key,Node* rightChild,const Compare& compare){
assert(!this->isLeaf());
int arg = this->search(key,compare);
for(int i=this->n;i>arg;i--){
this->keys[i] = this->keys[i-1];
this->ptr[i+1] = this->ptr[i];
}
this->keys[arg] = key;
this->ptr[arg+1] = rightChild;
this->n++;
}
// 更新节点
inline void update(Key key,Value value,const Compare& compare){
assert(this->isLeaf());
int arg = this->search(key,compare);
this->values[arg] = value;
}
// 删除节点及其右子树
inline void remove(Key key,const Compare& compare){
int arg = this->search(key,compare);
for(int i=arg;i<this->n-1;i++){
this->keys[i] = this->keys[i+1];
if(!this->isLeaf()) this->ptr[i+1] = this->ptr[i+2];
else this->values[i] = this->values[i+1];
}
if(!this->isLeaf()) this->ptr[this->n] = nullptr;
this->n--;
}
// 上溢出(n >= order)的时候调用,分裂成左右子树,自身变成左子树,返回右子树
inline Node* split(){
Node* newNode = new Node(this->isLeaf());
int mid = (order>>1);
if(this->isLeaf()){
for(int i=0,j=mid;j<this->n;i++,j++){
newNode->keys[i] = this->keys[j];
newNode->values[i] = this->values[j];
newNode->n++;
}
this->insertNextNode(newNode);
}else{
newNode->ptr[0] = this->ptr[mid+1];
this->ptr[mid+1] = nullptr;
for(int i=0,j=mid+1;j<this->n;i++,j++){
newNode->keys[i] = this->keys[j];
newNode->ptr[i+1] = this->ptr[j+1];
newNode->n++;
this->ptr[j+1] = nullptr;
}
}
this->n = mid;
return newNode;
}
// 非叶子节点下溢出(n < (order>>1))且兄弟无法借出节点时调用,和右兄弟合并
inline void merge(Key key,Node *rightSibling) {
assert(!this->isLeaf());
this->keys[this->n] = key;
this->ptr[this->n+1] = rightSibling->ptr[0];
this->n++;
for(int i=0;i<rightSibling->n;i++){
this->keys[this->n] = rightSibling->keys[i];
this->ptr[this->n+1] = rightSibling->ptr[i+1];
this->n++;
}
delete rightSibling;
}
// 叶子节点下溢出(n < (order>>1))且兄弟无法借出节点时调用,和右兄弟合并
inline void merge(Node *rightSibling){
assert(this->isLeaf());
for(int i=0;i<rightSibling->n;i++){
this->keys[this->n] = rightSibling->keys[i];
this->values[this->n] = rightSibling->values[i];
this->n++;
}
this->removeNextNode();
delete rightSibling;
}
// 双向链表中插入下一个叶子节点
inline void insertNextNode(Node *nextNode){
assert(this->isLeaf() && nextNode);
nextNode->ptr[1] = this->ptr[1];
if(this->ptr[1]) this->ptr[1]->ptr[0] = nextNode;
this->ptr[1] = nextNode;
nextNode->ptr[0] = this;
}
// 双向链表中删除下一个叶子节点
inline void removeNextNode(){
assert(this->isLeaf());
if(this->ptr[1]->ptr[1]) this->ptr[1]->ptr[1]->ptr[0] = this;
if(this->ptr[1]) this->ptr[1] = this->ptr[1]->ptr[1];
}
};
Compare compare;
int size;
Node *root;
Node *head;
std::stack<Node*> findNodeByKey(Key key);
void adjustNodeForUpOver(Node *node,Node* parent);
void adjustNodeForDownOver(Node *node,Node* parent);
void maintainAfterInsert(std::stack<Node*>& nodePathStack);
void maintainAfterRemove(std::stack<Node*>& nodePathStack);
public:
BPTree(){
assert(order>=3);
this->size = 0;
this->root = nullptr;
this->head = nullptr;
this->compare = Compare();
}
int insert(Key key,Value value);
int remove(Key key);
void leafTraversal();
void levelOrderTraversal();
};
/**
* @brief 查找含有key的节点(需保证根节点存在)
* @param key 键值
* @return 保存了查找路径的栈,栈顶即为含有key的节点
*/
template<int order,typename Key,typename Value,typename Compare>
std::stack<typename BPTree<order,Key,Value,Compare>::Node*> BPTree<order,Key,Value,Compare>::findNodeByKey(Key key){
std::stack<Node*> nodePathStack;
Node* node = this->root;
nodePathStack.push(node);
while(!node->isLeaf()){
int arg = node->search(key,this->compare);
if(arg<node->n && node->keys[arg]==key) arg++;
node = node->ptr[arg];
nodePathStack.push(node);
}
return nodePathStack;
}
/**
* @brief 插入键值对
* @param key 新的键
* @param value 新的值
* @return int 0表示插入成功,1表示节点已存在,更新value
*/
template<int order,typename Key,typename Value,typename Compare>
int BPTree<order,Key,Value,Compare>::insert(Key key,Value value){
if(this->root==nullptr){
this->root = new Node(true);
this->root->insert(key,value,this->compare);
this->head = this->root;
return 0;
}
std::stack<Node*> nodePathStack = this->findNodeByKey(key);
Node *node = nodePathStack.top();
if(node->hasKey(key,this->compare)){
node->update(key,value,this->compare);
return 1;
}
node->insert(key,value,this->compare);
this->maintainAfterInsert(nodePathStack);
this->size++;
return 0;
}
/**
* @brief 插入新数据后的维护
* @param nodePathStack 保存了因为插入而受到影响的节点的栈
* @return void
*/
template<int order,typename Key,typename Value,typename Compare>
void BPTree<order,Key,Value,Compare>::maintainAfterInsert(std::stack<Node*>& nodePathStack){
Node *node,*parent;
node = nodePathStack.top();nodePathStack.pop();
while(!nodePathStack.empty()){
parent = nodePathStack.top();nodePathStack.pop();
if(!node->isUpOver()) return ;
this->adjustNodeForUpOver(node,parent);
node = parent;
}
if(!node->isUpOver()) return ;
this->root = new Node(false);
parent = this->root;
parent->ptr[0] = node;
this->adjustNodeForUpOver(node, parent);
}
/**
* @brief 调整上溢出节点
* @param node 上溢出节点
* @param parent 上溢出节点的父亲
* @return void
*/
template<int order,typename Key,typename Value,typename Compare>
void BPTree<order,Key,Value,Compare>::adjustNodeForUpOver(Node *node,Node* parent){
// For node As LeafNode
// parent: ... ... ... mid ...
// / =====> / |
// node: [left] mid [right] [left] mid:[right]
// For node As Node
// parent: ... ... ... mid ...
// / =====> / |
// node: [left] mid [right] [left] [right]
//
int mid = (order>>1);
Key key = node->keys[mid];
Node *rightChild = node->split();
parent->insert(key,rightChild,this->compare);
}
/**
* @brief 根据键删除数据
* @param key 要被删除的数据的键
* @return int 0表示删除成功,1表示键不存在,删除失败
*/
template<int order,typename Key,typename Value,typename Compare>
int BPTree<order,Key,Value,Compare>::remove(Key key){
if(this->root == nullptr) return 1;
std::stack<Node*> nodePathStack = this->findNodeByKey(key);
Node *node =nodePathStack.top();
if(!node->hasKey(key,this->compare)) return 1;
node->remove(key,this->compare);
this->maintainAfterRemove(nodePathStack);
this->size--;
return 0;
}
/**
* @brief 删除数据后的维护
* @param nodePathStack 保存了因为删除而受到影响的节点的栈
* @return void
*/
template<int order,typename Key,typename Value,typename Compare>
void BPTree<order,Key,Value,Compare>::maintainAfterRemove(std::stack<Node*>& nodePathStack){
Node *node,*parent;
node = nodePathStack.top();nodePathStack.pop();
while(!nodePathStack.empty()){
parent = nodePathStack.top();nodePathStack.pop();
if(!node->isDownOver()) return ;
this->adjustNodeForDownOver(node,parent);
node = parent;
}
if(this->root->n) return ;
if(!this->root->isLeaf()) this->root = node->ptr[0];
else{
this->root = nullptr;
this->head = nullptr;
}
delete node;
}
/**
* @brief 调整下溢出节点
* @param node 下溢出的节点
* @param parent 下溢出节点的父亲
* @return void
*/
template<int order,typename Key,typename Value,typename Compare>
void BPTree<order,Key,Value,Compare>::adjustNodeForDownOver(Node *node,Node* parent){
int mid = ((order-1)>>1);
int arg=-1;
if(node->n) arg = parent->search(node->keys[0],this->compare);
else while(parent->ptr[++arg]!=node);
Node* left = arg > 0 ? parent->ptr[arg-1] : nullptr;
Node* right = arg < parent->n ? parent->ptr[arg+1] : nullptr;
// case 1: 左兄弟或右兄弟可以借出一个数据
if((left && left->n > mid) || (right && right->n > mid)){
// 左兄弟借出一个数据
// For node As LeafNode
// parent: ... key ... ... last ...
// / \ =====> / \
// [......]:last [node] [......] last:[node]
// For node As Node
// parent: ... key ... ... last ...
// / \ =====> / \
// [......]:last [node] [......] key:[node]
if(left && left->n > mid){
if(node->isLeaf()){
node->insert(left->keys[left->n-1],left->values[left->n-1],this->compare);
}else{
node->insert(parent->keys[arg-1],node->ptr[0],this->compare);
node->ptr[0] = left->ptr[left->n];
}
parent->keys[arg-1] = left->keys[left->n-1];
left->remove(left->keys[left->n-1],this->compare);
}
// 右兄弟借出一个数据
// For node As LeafNode
// parent: ... key ... ... second ...
// / \ =====> / \
// [node] first:second:[......] [node]:first second:[......]
// For node As Node
// parent: ... key ... ... first ...
// / \ =====> / \
// [node] first:[......] [node]:key [......]
else if(right && right->n > mid){
if(node->isLeaf()){
node->insert(right->keys[0],right->values[0],this->compare);
right->remove(right->keys[0],this->compare);
parent->keys[arg] = right->keys[0];
}else{
node->insert(parent->keys[arg],right->ptr[0],this->compare);
right->ptr[0] = right->ptr[1];
parent->keys[arg] = right->keys[0];
right->remove(right->keys[0],this->compare);
}
}
return ;
}
// case 2: 左兄弟或右兄弟都不能借出一个数据
if(left){
// 和左兄弟合并
// For node As LeafNode
// parent: ... key ... ... ...
// / \ =====> /
// [left] [node] [left]:[node]
// For node As Node
// parent: ... key ... ... ...
// / \ =====> /
// [left] [node] [left]:key:[node]
Key key = parent->keys[arg-1];
if(left->isLeaf()) left->merge(node);
else left->merge(key,node);
parent->remove(key,this->compare);
}
else if(right){
// 和右兄弟合并
// For node As LeafNode
// parent: ... key ... ... ...
// / \ =====> /
// [node] [right] [node]:[right]
// For node As Node
// parent: ... key ... ... ...
// / \ =====> /
// [node] [right] [node]:key:[right]
Key key = parent->keys[arg];
if(node->isLeaf()) node->merge(right);
else node->merge(key,right);
parent->remove(key,this->compare);
}
}
/**
* @brief B+树的叶层遍历
* @return void
*/
template<int order,typename Key,typename Value,typename Compare>
void BPTree<order,Key,Value,Compare>::leafTraversal(){
Node* p = this->head;
while(p){
for(int i=0;i<p->n;i++){
std::cout << p->keys[i] << ' ';
}
p = p->ptr[1];
}
std::cout << std::endl << std::endl;
}
/**
* @brief B+树的中序遍历
* @return void
*/
template<int order,typename Key,typename Value,typename Compare>
void BPTree<order,Key,Value,Compare>::levelOrderTraversal(){
std::queue<Node*> q;
q.push(this->root);
while(!q.empty()){
int layerSize = q.size();
while(layerSize--){
Node *node = q.front();q.pop();
if(!node) continue;
int i;
for(i=0;i<node->n;i++){
std::cout << node->keys[i] << " ";
if(!node->isLeaf()) q.push(node->ptr[i]);
}
std::cout << " | ";
if(!node->isLeaf()) q.push(node->ptr[i]);
}
std::cout << std::endl;
}
}
int main(){
int a[] ={70, 20, 150, 90, 40, 130, 10, 180, 60, 110, 30, 200, 80, 170, 50, 140, 100, 160, 120, 190};
BPTree<6,int,int> mp;
for(auto p:a){
mp.insert(p,0);
mp.levelOrderTraversal();
mp.leafTraversal();
}
for(auto p:a){
mp.remove(p);
mp.levelOrderTraversal();
mp.leafTraversal();
}
}