【BTree、B-树】B树的C++实现

一、B树的概念

B树是平衡的多叉树,一个节点有多于两个(不能小于)结点的平衡多叉树。

由于B树倒着生长所以平衡。

缺点:浪费空间

二、B树满足以下性质:

1、根结点至少有两个孩子。[2,M]个孩子
2、每个非根结点有【(M/2),M】个孩子。
3、每个非根结点有【(M/2-1),M-1】个关键字,并且以升序排序。
4、每个结点孩
5、key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间
6、所有的叶子结点在同一层

三、结点的构造:

由于B树的性质,我们写出了如下每个结点应该包含的内容。

  • 包含大小为M-1的kvs数组,但是要方便我们后续的插入分裂操作,所以多开一个结点方便我们分裂 ,即大小为M的kvs数组
  • 包含了父节点指针
  • 包含结点指针类型的数组,数组中存放的是孩子指针
  • 包含size_t参数,代表实际关键字数量
  • 最后写上构造函数

四、树的构造:

(1)查找结点:

查找结点返回值是一个pair,如果找到了结点,返回结点cur和此结点的位置,没有找到则返回父节点和-1。

查找的具体过程就是遍历结点构成的数组即可,需要注意的是边界条件,同时在遍历过程中看在树的左边和后面。

(2)插入:

分裂原理:

如果M为3设置数组大小为3(注意不能为2),当结点数为3时候要分裂,左右分裂的时候,先找中位数,中位数右边的结点分一半,中位数左边的结点分一半,中位数占一个,为父亲结点。

需要注意的是:

不能向非叶子结点插入
为了让结点插入传入的是pair K,V

插入方法:

①如果ROOT为空,则构造结点直接插入,需要注意的是调整size

②对树进行查找,调用查找函数,接收返回值。

③由于返回值是一个pair,所以判断返回值的第二个参数,如果存在,则参数为大于等于0,插入不成功返回false,否则进行插入。

④没有找到的时候返回pair的第一个参数是插入节点的父亲结点,构造节点调用insertKV(此处逻辑复杂,单独写出来)进行插入。

⑤判断插入后结点的size值,如果小于M则直接插入成功,反之需要进行分裂。

⑥分离时调用DivideNode函数,

《【BTree、B-树】B树的C++实现》
《【BTree、B-树】B树的C++实现》

如上两图把分裂的情况全部列举出来,这便是B树实现的难点所在,理解了分裂,便理解了插入。

B+树在没有结点的时候是直接创建两个

五、代码实现:

#include <iostream>
using namespace std;

template<class K, class V, size_t M>
struct BTreeNode
{
    pair<K, V> _kvs[M];   // 多开一个空间,方便分裂
    BTreeNode<K, V, M>* _subs[M+1];
    BTreeNode<K, V, M>* _parent;

    size_t _size; // 关键字的数量

    BTreeNode()
        :_parent(NULL)
        ,_size(0)
    {
        for (size_t i = 0; i < M+1; ++i)
        {
            _subs[i] = NULL;
        }
    }
};

template<class K, class V, size_t M>
class BTree
{
    typedef BTreeNode<K, V, M> Node;
public:
    BTree()
        :_root(NULL)
    {}

    pair<Node*, int> Find(const K& key)
    {
        Node* parent = NULL;
        Node* cur = _root;
        while (cur)
        {
            size_t i = 0;
            while(i < cur->_size)
            {
                if (cur->_kvs[i].first > key) // 在[i]的左树
                    break;
                else if (cur->_kvs[i].first < key) // 在后面
                    ++i;
                else
                    return make_pair(cur, i);
            }

            parent = cur;
            cur = cur->_subs[i];
        }

        return make_pair(parent, -1);
    }

    bool Insert(const pair<K, V>& kv)
    {
        if (_root == NULL)
        {
            _root = new Node;
            _root->_kvs[0] = kv;
            _root->_size = 1;

            return true;
        }

        pair<Node*, int> ret = Find(kv.first);
        if (ret.second >= 0)
        {
            return false;
        }

        Node* cur = ret.first;
        pair<K, V> newKV = kv;
        Node* sub = NULL;

        // 往cur插入newKV, sub
        while (1)
        {
            InsertKV(cur, newKV, sub);

            if (cur->_size < M)
            {
                return true;
            }
            else 
            {
                // 分裂
                Node* newNode = DivideNode(cur);

                pair<K, V> midKV = cur->_kvs[cur->_size/2];
                cur->_size -= (newNode->_size+1);

                // 1.根节点分裂
                if (cur == _root)
                {
                    _root = new Node;
                    _root->_kvs[0] = midKV;
                    _root->_size = 1;
                    _root->_subs[0] = cur;
                    _root->_subs[1] = newNode;
                    cur->_parent = _root;
                    newNode->_parent = _root;
                    return true;
                }
                else
                {
                    sub = newNode;
                    newKV = midKV;
                    cur = cur->_parent;
                }
            }
        }
    }

    //分裂结点
    Node* DivideNode(Node* cur)
    {
        Node* newNode = new Node;
        int mid = cur->_size/2;

        size_t j = 0;
        size_t i = mid+1;
        for (; i < cur->_size; ++i)
        {
            //此处体现了kvs的作用
            newNode->_kvs[j] = cur->_kvs[i];
            newNode->_subs[j] = cur->_subs[i];
            if(newNode->_subs[j])
                newNode->_subs[j]->_parent = newNode;
            newNode->_size++;
            j++;
        }

        newNode->_subs[j] = cur->_subs[i];
        if(newNode->_subs[j])
            newNode->_subs[j]->_parent = newNode;

        return newNode;
    }

    //单独写出来,逻辑比较复杂
    void InsertKV(Node* cur, const pair<K, V>& kv, Node* sub)
    {
        int end = cur->_size-1;
        while (end >= 0)
        {
            if (cur->_kvs[end].first > kv.first)
            {
                cur->_kvs[end+1] = cur->_kvs[end];
                cur->_subs[end+2] = cur->_subs[end+1];
                --end;
            }
            else
            {
                break;
            }
        }

        cur->_kvs[end+1] = kv;
        cur->_subs[end+2] = sub;
        if(sub)
            sub->_parent = cur;

        cur->_size++;
    }
    //中序遍历
    void InOrder()
    {
        _InOrder(_root);
        cout<<endl;
    }

    void _InOrder(Node* root)
    {
        if (root == NULL)
            return;

        Node* cur = root;
        size_t i = 0;
        for (; i < cur->_size; ++i)
        {
            _InOrder(cur->_subs[i]);
            cout<<cur->_kvs[i].first<<" ";
        }

        _InOrder(cur->_subs[i]);
    }

private:
    Node* _root;
};

void TestBTree()
{
    BTree<int, int, 3> t;
    int a[] = {53, 75, 139, 49, 145, 36, 101};
    for (size_t i = 0; i < sizeof(a)/sizeof(a[0]); ++i)
    {
        t.Insert(make_pair(a[i], i));
    }

    t.InOrder();
}

六、B树应用:

B和B+树主要用在文件系统以及数据库做索引.比如Mysql;

    原文作者:B树
    原文地址: https://blog.csdn.net/ArchyLi/article/details/78632245
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞