数据结构——B+树(附C++实现代码)

定义

一个 m 阶的B+树是一个有以下属性的树:

  1. 每一个节点最多有 m 个子节点
  2. 每一个非叶子节点(除根节点)最少有 ⌈m/2⌉ 个子节点
  3. 如果根节点不是叶子节点,那么它至少有两个子节点
  4. k 个子节点的非叶子节点拥有 k − 1 个键
  5. 所有的叶子节点都在同一层
  6. 所有的键值对(即数据记录)都存储在叶子节点中,非叶子节点仅存储键(索引)用于路由。
  7. 所有的叶子节点通过指针连接成一个有序链表,便于范围查询和顺序访问。
  8. 非叶子节点中的键也会出现在叶子节点中,作为索引。

注:由于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();
};
叶子节点维护的信息
成员名类型描述
nint节点数量
IS_LEAFbool是否为叶子节点
keysKey[]节点键值的数组,具有唯一性和可排序性
valuesValue*叶子节点保存的值的数组
ptrNode**对于叶子节点而言,其为双向链表中节点指向前后节点的指针,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+树的插入

具体流程
  1. 首先检查根节点是否存在,若不存在则创建根节点并插入新的数据,之后将头节点设置为根节点,返回
  2. 查找要插入的键是否已存在,若存在,则直接更新数据并返回
  3. 向叶子节点直接插入数据
  4. 插入新的数据后可能会导致节点上溢出,因此需要对插入新数据后所有受到影响的节点(也就是查询路径上的节点)进行维护
实现代码
/**
 * @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+树的删除

具体流程
  1. 首先检查根节点是否存在,若不存在,则删除失败
  2. 检查被删除的键是否存在,若不存在,则删除失败
  3. 查找到的节点一定为叶子节点,可直接节点中删除键为key的数据
  4. 删除数据后可能会导致节点下溢出,因此需要对删除数据后所有受到影响的节点(也就是查询路径上的节点)进行维护
实现代码
/**
 * @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();
	}
}
### B+数据结构C++中的实现 #### 定义B+节点类 为了构建一个完整的B+,首先定义内部节点和叶节点的公共基类`BPNode`以及派生的具体节点类型。 ```cpp #include <iostream> #include <vector> class BPNode { protected: bool is_leaf; public: virtual ~BPNode() {} virtual void insert(int key) = 0; }; // 叶子节点 class LeafNode : public BPNode { private: std::vector<int> keys; LeafNode* next; public: explicit LeafNode(LeafNode* n = nullptr):is_leaf(true), next(n) {} void insert(int key); friend class BPTree; }; // 内部节点 class InternalNode : public BPNode { private: std::vector<BPNode*> children; std::vector<int> keys; public: explicit InternalNode():is_leaf(false) {} void insert(int key); friend class BPTree; }; ``` #### 插入方法实现 对于不同类型的节点,插入逻辑有所不同: - **叶子节点**负责存储实际的关键字并维护有序列表; - **内部节点**则管理指向其他节点(可以是另一个内部节点或叶子节点)的指针数组。 ```cpp void LeafNode::insert(int key){ auto it = std::lower_bound(keys.begin(), keys.end(), key); if(it != keys.end()){ // 如果存在相同键,则更新该位置上的值或其他处理方式 *it = key; }else{ // 否则添加新元素保持顺序不变 keys.insert(it, key); // 更新链表关系 while(next && !next->keys.empty() && this->keys.back()>next->keys.front()){ int tmp = this->keys.back(); this->keys.pop_back(); next->keys.insert(next->keys.begin(),tmp); break; } } } void InternalNode::insert(int key){ size_t i=0; for(;i<children.size();++i){ if(key<=keys[i]){ break; } } children[i]->insert(key); // 处理溢出情况... } ``` #### 构建B+类 最后封装整个B+的功能接口,包括但不限于插入、删除、查询等操作。 ```cpp class BPTree { private: BPNode* root_; unsigned int min_degree_; public: BPTree(unsigned int t):min_degree_(t){ root_ = new LeafNode(); } ~BPTree(){ delete root_; } void Insert(const int& value){ root_->insert(value); // 当根节点分裂时调整根节点 if(/* 根节点满 */){ InternalNode* old_root = dynamic_cast<InternalNode*>(root_); InternalNode* new_root = new InternalNode(); // 将旧根分成两部分并将中间项提升至新的根上 split_child(new_root,old_root/*...*/); root_=new_root; } } private: void split_child(/* 参数 */) {/* ... */} }; ``` 上述代码展示了如何基于面向对象编程的思想设计一个简单的B+数据结构及其核心功能之一——插入操作。需要注意的是这只是一个简化版本,在真实应用中还需要考虑更多细节如边界条件检查、内存分配异常安全等问题[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值