B树的原理及实现

B树的原理及实现

B树的作用

B树是一种存放在磁盘中的数据结构,是为了减少磁盘读写的次数,同时也不影响查找效率而设计的。B+树在B树的基础上可以实现链式遍历。B树每个结点具有key值数组和索引数组,将数组大小定位页的倍数可以使计算机方便读取每一个结点。索引比key值多一个,相当于每一个key值都在两个索引中间,左边的索引小于key值,右边的索引大于key值,每一次都可以查找到需要读取的结点的索引地址,所以可以有效的减少磁盘读取次数。
在数据库中大量的使用B+树作为索引存储的数据结构。

B树的原理

B树有一个固定的阶数(M),该阶数一般为页的倍数,方便磁盘每次读取。
B树具有以下性质:

    1.每个结点至多拥有M颗子树
    2.根结点至少用于两颗子树
    3.除了根结点以外,其余每个分支结点至少拥有M/2颗子树
    4.所有的叶结点都在同一层上
    5.有k颗子树的分支结点则存在k-1个关键字,关键字按照递增顺序进行排序
    6.关键字数量满足ceil(M / 2) - 1 <= n <= M - 1

B树可视化展示

当一个结点数量超过M-1时,需要进行分裂,将结点分开。
结点的最小值公式:

	if M mod 2 == 0
		leftNum =  (M - 1) / 2
	else
		leftNum = M / 2

分裂:

  • 创建右边结点(right),将左结点右边(结点的最小值 + 2开始)的信息拷贝到右结点里。
  • 如果不是叶子结点,拷贝对应的子结点信息。
  • 如果是叶子结点,将右节点也设置为叶子结点。
  • 如果当前的结点是根结点,创建新的根结点,将左右结点的父结点指向新的根结点,并添加根结点的索引,将中间值上移至根结点中。
  • 如果不是根结点,需要判断左边结点在根结点中所处的位置,将右边结点的索引插入到左边结点后面,并将中间key值插入至左边结点的后面。

除了根结点外的结点,如果数量小于最小数量(结点的最小值),则需要进行调整,可以向左右兄弟借值。如果借不到则需要进行合并。

合并:

  • 将左右结点合并,需要将父结点的值下沉到左边结点,删除父结点中对应的key值和value,删除右边结点的索引信息。
  • 将右结点的信息向左结点后面添加,如果不是叶结点需要将索引也拷贝到左结点中。

接下来就谈谈怎么插入。
插入:

  • 当B树是空树,则直接插入至头结点。
  • 如果不是空树,则需要依照key值向下寻找,直到找到叶结点为止,插入到叶结点中。向下寻找就是通过key值判断。从根结点开始,可以从前开始遍历,如果小于key值则进入到key值左边的索引向下继续,
    如果大于key值则向后继续遍历,直到最后一个key值。也可以从后开始遍历,如果大于就进入到key值右边的索引向下继续,如果小于key值则向前继续遍历,直到第一个key值为止。
  • 如果当前结点是叶结点,直接将数值插入到相应的位置,使用插入排序的思想就行(待插入key从后向前比较,如果待插入key小则将数据向后移动,直到待插入key大于或等于停止)。
  • 循环向上判断,从插入的结点开始,如果key值数量超过M - 1,则将结点分裂。又因为分裂需要向父结点插值,所以分裂后继续判断父结点,依次类推,循环向上。

插入完后就是删除了。
删除:

  • 首先查找key是否存在,不存在就删除失败。
  • 如果删除的结点不是叶子结点,找到比当前结点小的最大叶结点,key左边索引树的最右子树。
    这个结点是实际删除的结点,找到最后一个key,将其key和value覆盖逻辑上需要删除的结点,将其结点key数减一。
  • 如果删除的结点就是叶子结点,将该key值后面的数据向前覆盖。
  • 判断实际删除的结点,key数是否小于结点的最小值,如果小于结点最大小值,则进行修复。
  • 修复完wDelete后继续向上修复,将wDelete指向父结点。
  • 如果 根结点为空或者(wDelete是根结点 并且 wDeleet的key数大于0)就结束修复。

删除修复:

  • 判断wDelete是否为根结点,
  • 是根节点又是叶子结点的直接删除根结点(因为进入删除修复的根结点key数一定为0)。
  • 是根节点不是叶结点,将根结点指向第一个索引(这种情况一定是根结点只有两个子树进行了合并,只剩下一个子树了)。
  • 剩下的情况wDelete不是根结点,一定有父结点。
  • 查看自己是父结点的第几个孩子(n - 1)。
  • 如果有左兄弟并且左兄弟的key数大于结点的最小值,则向左兄弟借值,将父结点第一个小于本节点最小值的key(n)下沉,插入到本结点最左,将左兄弟结点的最大key上移。
  • 如果有右兄弟并且右兄弟的key数大于结点的最小值,则向右兄弟借值,将父结点的第一个大于本节点的最大值的key(n)下沉,插入到本结点最后,将右兄弟第一个key上移。
  • 如果左右兄弟的key数都刚好等于结点的最小值。
  • 有左兄弟就和左兄弟合并
  • 有右兄弟就和右兄弟合并

二叉树和红黑树的实现

我是按照对原理的理解,和观看相关博客、伪代码,又结合了泛型编程的方式,自己实现的二叉树和红黑树。算法流程和算法导论中的不完全一致,没有算法导论简洁。仅供参考,具体代码如下:

#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
using namespace std;

/* $begin bTree */
/*
    一颗M阶B树T,满足以下条件
    1.每个结点至多拥有M颗子树
    2.根结点至少用于两颗子树
    3.除了根结点以外,其余每个分支结点至少拥有M/2颗子树
    4.所有的叶结点都在同一层上
    5.有k颗子树的分支结点则存在k-1个关键字,关键字按照递增顺序进行排序
    6.关键字数量满足ceil(M / 2) - 1 <= n <= M - 1
*/
// B树的关系特性
template <typename __TYPE>
struct BCharacter
{
    __TYPE* parent;
    __TYPE** children;
};

// B树的结点
template <typename __KEY, typename __VALUE>
struct BNode
{
    BCharacter<BNode<__KEY, __VALUE>> bCharacter;
    __KEY* key;
    __VALUE* value;
    int num;
    bool leaf;
};

// B树类
template <typename __KEY, typename __VALUE>
class BTree
{
public:
    typedef BNode<__KEY, __VALUE> BNodeT;
    typedef BTree<__KEY, __VALUE> BTreeT;
    
    // 初始化为空树
    BTree(int m) noexcept
     : root(nullptr), M(m){}

    // 销毁树
    ~BTree() noexcept
    {
        destroyTreeAction(root);
    }

    // 树是空
    bool 
    isEmpty() noexcept
    {

        if(root == nullptr)
            return true;
        else
            return false;
    }

    // 插入B树结点
    bool 
    insertNode(__KEY p_key, __VALUE p_value) noexcept
    {
        // 当B树为空时,创建头结点
        if(isEmpty())
        {
            root = createNode();
            root->leaf = true;
        }

        BNodeT* cur = root;

        int i;
        while(cur->leaf == false)
        {// 当前结点不为叶结点,依照key向下寻找,直到找到叶结点
            
            for(i = cur->num - 1; p_key <= cur->key[i] && i >= 0;i--);
            cur = cur->bCharacter.children[i + 1];
        }

        // 当前结点为叶结点,将数值插入到相应的位置
        for(i = cur->num - 1; p_key < cur->key[i] && i >= 0;i--)
        {
            cur->key[i + 1] = cur->key[i];
            cur->value[i + 1] = cur->value[i];
        }
        cur->key[i + 1] = p_key;
        cur->value[i + 1] = p_value;
        cur->num++;

        while(cur->num == M)
        {// 超出了M - 1,将叶子结点分裂,因为分裂向父结点插值,
         // 所以继续判断父结点是否超出M - 1
            bTreeSplit(cur);
            cur = cur->bCharacter.parent;
        }
        
        return false;
    }

    // 查找B树结点的value
    bool findNode(__KEY p_key, __VALUE& p_value) noexcept
    {
        int i = 0;
        BNodeT* res = getNode(p_key, i);
        if(res == nullptr)return false;

        p_value = res->value[i];
        
        return true;
    }

    // 修改B树结点
    bool updateNode(__KEY p_key, __VALUE p_value) noexcept
    {
        int i = 0;
        BNodeT* res = getNode(p_key, i);
        if(res == nullptr)return false;

        res->value[i] = p_value;
        
        return true;
    }
    
    // 删除B树结点
    bool deleteNode(__KEY p_key) noexcept
    {
        int index = 0;
        BNodeT* res = getNode(p_key, index);
        if(res == nullptr)return false;

        BNodeT* wDelete = res;

        if(wDelete->leaf == false)
        {// 如果待删除结点不是叶结点,找到比当前结点小的最大叶结点
            wDelete = wDelete->bCharacter.children[index];
            while(wDelete->leaf == false)
            {// 一直向最大子树寻找
                wDelete = wDelete->bCharacter.children[wDelete->num];
            }

            // 待删除结点为叶结点
            res->key[index] = wDelete->key[wDelete->num - 1];
            res->value[index] = wDelete->value[wDelete->num - 1];
            wDelete->num--;
        }
        else
        {// 待删除结点是叶结点
            wDelete->num--;
            for(int i = index; i < wDelete->num; i++)
            {
                wDelete->key[i] = wDelete->key[i + 1];
                wDelete->value[i] = wDelete->value[i + 1];
            }
        }

        // 修复B树
        while(wDelete->num < getMinNum()) 
        {// 小于最小结点数,需要修复
            deleteFixUp(wDelete);
            if(wDelete != root)
                wDelete = wDelete->bCharacter.parent;
            if(root == nullptr || 
               (wDelete == root && wDelete->num > 0))break;
        }
        return true;
    }
    
    // 销毁B树
    bool destroyTree() noexcept 
    {return destroyTreeAction(root);};
    // 遍历B树
    bool dispBinTree() noexcept 
    {return dispBinTreeAction(root);};
    // 展示B树结构
    bool showBinTree() noexcept 
    {return showBinTreeAction(root, 1);};

private:
    // 分裂结点
    void bTreeSplit(BNodeT* cur)
    {
        BNodeT* left = cur, *right = createNode(), *parent;
        int lNum = getMinNum() - 1, 
            rNum = lNum + 2;

        int j;
        for(int i = rNum, j = 0; i < M; i++, j++)
        {
// 将左结点右边的信息拷贝至右结点
            right->key[j] = left->key[i];
            right->value[j] = left->value[i];
        }
        if(cur->leaf == false)
        {// 不是叶子结点
            for(int i = rNum, j = 0; i <= M; i++, j++)
            {
// 拷贝对应的子结点信息
                right->bCharacter.children[j] = 
                left->bCharacter.children[i];
                left->bCharacter.children[i] = nullptr;
                right->bCharacter.children[j]->bCharacter.parent = right; 
            }
        }
        else
        {// 是叶子结点,将右结点置为叶子结点
            right->leaf = true;   
        }
        left->num = lNum + 1;
        right->num = M - rNum;

            
        if(cur == root)
        {// 如果是根结点需要开辟两个新结点
            BNodeT* r = createNode();
            root = r;
            r->bCharacter.parent = nullptr;

            // 修复对应的结点关系
            left->bCharacter.parent = r;
            r->bCharacter.children[0] = left;
        }

        right->bCharacter.parent = left->bCharacter.parent;
        parent = left->bCharacter.parent;

        int i;
        // 将数值和关系腾出位置,并找到相应位置
        for(i = parent->num; left != parent->bCharacter.children[i] && i >= 0;i--)
        {
            parent->key[i] = parent->key[i - 1];
            parent->value[i] = parent->value[i - 1];
            parent->bCharacter.children[i + 1] = 
            parent->bCharacter.children[i];
        }
        parent->key[i] = left->key[lNum + 1];
        parent->value[i] = left->value[lNum + 1];
        parent->bCharacter.children[i + 1] = right;
        parent->num++;
    }

    bool mergeNode(BNodeT* parent, int l, int r)
    {
        BNodeT* left = parent->bCharacter.children[l], 
              *right = parent->bCharacter.children[r];
        int i, j;

        // 将父结点的值下沉
        left->value[left->num] = parent->value[l];
        left->key[left->num++] = parent->key[l];
        if(parent->num > 0)parent->num--;
        
        for(i = l; i < parent->num; i++)
        {
            parent->key[i] = parent->key[i + 1];
            parent->value[i] = parent->value[i + 1];
            parent->bCharacter.children[i + 1] = 
                parent->bCharacter.children[i + 2];
        }
        parent->bCharacter.children[i + 1] = 
            parent->bCharacter.children[i + 2];

        
        // 将右结点的值拷贝至左结点
        for(i = left->num, j = 0; j < right->num; i++, j++)
        {
            left->key[i] = right->key[j];
            left->value[i] = right->value[j];
            left->num++;
            if(left->leaf == false)
            {// 左孩子不是叶结点
                left->bCharacter.children[i] = 
                    right->bCharacter.children[j];
                left->bCharacter.children[i]->bCharacter.parent = left;
            }
        }
        if(left->leaf == false)
        {// 左孩子不是叶结点
            left->bCharacter.children[i] = 
                    right->bCharacter.children[j];
            left->bCharacter.children[i]->bCharacter.parent = left;
        }
        
        destroyNode(right);

        return true;
    }

    bool deleteFixUp(BNodeT*& wDelete)
    {
        if(wDelete == root)
        {
            if(root->leaf == true)
            {
                destroyNode(root);
                root = nullptr;
                return true;
            }
            root = root->bCharacter.children[0];
            destroyNode(wDelete);
            wDelete = root;
            return true;
        }
    
        BNodeT* parent = wDelete->bCharacter.parent, 
            *lBrother, *rBrother;
        int n = 0;
        // 查看自己是父结点的第几个孩子
        for(n = 0; wDelete != parent->bCharacter.children[n] &&
                   n <= parent->num; n++);        
        
        // 1.尝试借位
        if(n != 0)
        {// 有左兄弟
            lBrother = parent->bCharacter.children[n - 1];

            if(lBrother->num > getMinNum())
            {// 有多的结点
                int i;
                // 将父结点值下沉
                for(i = 0; i < wDelete->num; i++)
                {
                    wDelete->key[i + 1] = wDelete->key[i];
                    wDelete->value[i + 1] = wDelete->value[i];
                    wDelete->bCharacter.children[i + 1] =
                        wDelete->bCharacter.children[i];
                }
                wDelete->bCharacter.children[i + 1] =
                    wDelete->bCharacter.children[i];
                
                wDelete->key[0] = parent->key[n - 1];
                wDelete->value[0] = parent->value[n - 1];                                    
                wDelete->num++;

                // 将左兄弟最大值上移
                lBrother->num--;
                parent->key[n - 1] = lBrother->key[lBrother->num];
                parent->value[n - 1] = lBrother->value[lBrother->num];        
                // 将左兄弟最有子结点放在右兄弟最左
                wDelete->bCharacter.children[0] = 
                    lBrother->bCharacter.children[lBrother->num];
                
                return true;
            }
        }
        if(n != parent->num)
        {// 有右兄弟

            int i;
            rBrother = parent->bCharacter.children[n + 1];

            if(rBrother->num > getMinNum())
            {// 有多的结点
                // 将父结点值下沉
                wDelete->key[wDelete->num] = parent->key[n];
                wDelete->value[wDelete->num] = parent->value[n];
                wDelete->num++;

                // 将右兄弟最小值上移
                rBrother->num--;
                parent->key[n] = rBrother->key[0];
                parent->value[n] = rBrother->value[0]; 
                wDelete->bCharacter.children[wDelete->num] =
                    rBrother->bCharacter.children[0];
                // 将右兄弟第一个结点和子树移除
                for(i = 0; i < rBrother->num; i++)
                {
                    rBrother->key[i] = rBrother->key[i + 1];
                    rBrother->value[i] = rBrother->value[i + 1];
                    rBrother->bCharacter.children[i] = 
                        rBrother->bCharacter.children[i + 1];
                }
                rBrother->bCharacter.children[i] = 
                    rBrother->bCharacter.children[i + 1];
                return true;
            }            
        }
        
        // 2.合并
        if(n != 0)
        {// 和左兄弟合并
            wDelete = parent->bCharacter.children[n - 1];
            mergeNode(parent, n - 1, n);
            return true;
        }
        else
        {// 和右兄弟合并
            wDelete = parent->bCharacter.children[n];
            mergeNode(parent, n, n + 1);
            return true;
        }

        
        return true;
    }

    // 得到最小数量
    int getMinNum()
    {
        if(M % 2 == 0)return (M - 1) >> 1;
        return M >> 1;
    }

    // 得到B树结点和索引
    BNodeT* getNode(__KEY p_key, int& index) noexcept
    {
        BNodeT* res = root;
        
        int i;
        while(res != nullptr)
        {// 当前结点不为叶结点,依照key向下寻找,直到找到叶结点或找到key
            
            for(i = res->num - 1; p_key <= res->key[i] && i >= 0;i--)
            {
                if(p_key == res->key[i])
                {
                    index = i;
                    return res;
                }
            }
            if(res->leaf == true)return nullptr;
            res = res->bCharacter.children[i + 1];
        }
        return nullptr;
    }

    // 创建新结点
    BNodeT* 
    createNode() noexcept
    {
        // 创建当前结点并初始化
        BNodeT* cur = new BNodeT;
        cur->value = new __VALUE[M];
        cur->key = new __KEY[M];
        cur->num = 0;
        cur->leaf = false;
        cur->bCharacter.parent = nullptr;
        cur->bCharacter.children = new BNodeT*[M + 1];

        for(int i = 0; i <= M; i++)
            cur->bCharacter.children[i] = nullptr;
        return cur;
    }

    // 销毁叶结点
    bool
    destroyNode(BNodeT*& cur) noexcept
    {
        if(cur == nullptr) return false;
        BNodeT* wDelete = cur;
        delete[] cur->value;
        delete[] cur->key;
        delete[] cur->bCharacter.children;
        delete cur;
        cur = nullptr;
        return true;
    }
    
    // 后序遍历销毁二叉树
    bool 
    destroyTreeAction(BNodeT* cur) noexcept
    {
        if(cur == nullptr)return false;
        for(int i = 0; i < cur->num + 1; i++)
            destroyTreeAction(cur->bCharacter.children[i]);
        destroyNode(cur);
        return true;
    }

    // 采用中序遍历输出顺序序列
    bool 
    dispBinTreeAction(BNodeT* cur)
 noexcept
    {
        if(cur == nullptr)return false;
        dispBinTreeAction(cur->bCharacter.children[0]);

        for(int i = 1; i < cur->num + 1; i++)
        {
            cout << endl << "key :\t" << cur->key[i - 1] 
                << "\tvalue :\t" << cur->value[i - 1] << endl;
            dispBinTreeAction(cur->bCharacter.children[i]);
        }
        
        return true;
    }
    
    // 采用先序遍历输出二叉树结构
    bool 
    showBinTreeAction(BNodeT* cur,int deep)
 noexcept
    {
        if(cur == nullptr)return false;

        cout << endl << "deep : " << deep << endl;
        cout << "key : \t";
        for(int i = 0; i < cur->num; i++)
             cout << cur->key[i] << "\t";
        cout << endl;

        cout << "value : \t";
        for(int i = 0; i < cur->num; i++)
             cout << cur->value[i] << "\t";
        cout << endl;

        for(int i = 0; i < cur->num + 1; i++)
            showBinTreeAction(cur->bCharacter.children[i], deep + 1);
        
        return true;
    }

    BNodeT* root;
    int M;
};

/* $end bTree */

template <typename TreeT>
void test(TreeT& t)
{
    while(1)
    {
        int op = 0;
        int key;
        cout << endl << "cin operator(1.insert 2.delete) : ";
        cin >> op;
        if(op == 1)
        {
            cout << "insert key: ";
            cin >> key;
            t.insertNode(key, key + 96);

            t.dispBinTree();
            t.showBinTree();
        }
        else if(op == 2)
        { 
            cout << "delete key : ";
            cin >> key;
            t.deleteNode(key);

            t.dispBinTree();
            t.showBinTree();
        }
        
    }
}

int main(int argc, char **argv)
{
    BTree<int, char> t(6);
//    test(t);
/*
    t.insertNode(5, 5);
    t.insertNode(4, 4);
    t.insertNode(2, 2);
    t.insertNode(3, 3);
    t.insertNode(7, 7);
    t.insertNode(6, 6);
    t.insertNode(8, 8);

    t.dispBinTree();
    t.showBinTree();
    */
    test(t);
    
    return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殇弑天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值