1. 程式人生 > >【BTree、B-樹】B樹的C++實現

【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函式,

這裡寫圖片描述
這裡寫圖片描述

如上兩圖把分裂的情況全部列舉出來,這便是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;