【BTree、B-樹】B樹的C++實現
阿新 • • 發佈:2018-12-25
一、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;