B樹也是一種搜索樹,二叉搜索樹、紅黑樹、都是動態查找樹,典型的二叉搜索結構,查找的時間複雜度和樹的高度相關O(log2N)。
這些二叉搜索結構有一個共同的缺陷:數據量大,樹的高度太高,增大訪問磁盤的次數,從而效率低下。
想要加速對數據的訪問速度:
1.提高I/O的時間
2.降低樹的高度——平衡多叉樹
B樹的定義
一棵M階(M>2)的B樹,是一棵平衡的M路平衡搜索樹,可以是空樹或者滿足以下性質:
1.根節點至少有兩個孩子。
2.每個非根節點至少有M/2(上取整)個孩子,至多有M個孩子。
3.每個非根節點至少有M/2-1(上取整)個關鍵字,至多有M-1個關鍵字,並且以升序排列
4.key[i]和key[i+1]之間的孩子節點的值介於key[i]、key[i+1]之間。
5.所有的葉子節點都在同一層。
例:
插入{53,75,139,49,145,36,101};
M階B樹——M=3
3階B樹的插入過程:
代碼實現過程:
1.首先設定結構體,實際只用到兩個關鍵碼,但是爲了交換簡單,我們設置三個關鍵碼,孩子指針域始終比關鍵碼多一個。
2.創建一個類將B樹的函數操作封裝起來。
3.B樹的插入:
(1)根節點爲空,開闢一個新節點newNode,將key的值賦給newNode,對size+1,最後使根指向newNode。
(2)根不爲空,通過Find函數找到插入位置cur,通過_InsertKey()函數將key的值賦給cur,(cur->size)+1,判斷cur->size與M的大小,如果cur->size < M則return true;
(3)如果cur->size=M,要將節點進行分裂,將中間的關鍵碼向上提,new一個節點將最右邊的關鍵碼賦值給key[0]。
(4)每次改變節點要記得更改雙親的指向。
【BTree.h】
#pragma once
#include<iostream>
using namespace std;
#define M 3
template<class K>
struct BTreeNode
{
K _keys[M]; //關鍵碼
BTreeNode<K>* _pSub[M + 1]; //孩子指針域
BTreeNode<K>* _pParent;
size_t size;
BTreeNode()
:_pParent(NULL)
, size(0)
{
for (int i = 0; i < (M + 1); i++)
_pSub[i] = NULL;
}
};
template<class K>
class BTree
{
typedef BTreeNode<K> Node;
typedef Node* pNode;
public:
BTree()
:pRoot(NULL)
{}
bool BTree_Insert(K value)
{
if (pRoot == NULL)
{
pRoot = new Node;
pRoot->_keys[0] = value;
pRoot->size = 1;
return true;
}
//節點的關鍵字滿了就進行分裂
pair<pNode,int> findNode = _Find(value);
if (findNode.second >= 0) //找到相同關鍵碼
return false;
//沒找到相同節點,可以插入新節點
pNode cur = findNode.first;
K newkey = value;
pNode sub = NULL;
//向cur插入newkey,sub
while (1)
{
_InsertKey(cur, newkey, sub); //插入一個孩子和一個關鍵字
if (cur->size < M)
return true;
else
{
//需要分裂
pNode newNode = _splitblock(cur);
K midkey = cur->_keys[(cur->size) / 2];
//根節點分裂
cur->size = (cur->size) - (newNode->size + 1);
if (cur == pRoot)
{
pRoot = new Node;
pRoot->_keys[0] = midkey;
pRoot->size = 1;
pRoot->_pSub[0] = cur;
pRoot->_pSub[1] = newNode;
cur->_pParent = pRoot;
newNode->_pParent = pRoot;
return true;
}
else
{
sub = newNode;
newkey = midkey;
cur = cur->_pParent;
}
}
}
}
void InOrder() //中序遍歷
{
_InOrder(pRoot);
cout << endl;
}
private:
pNode _splitblock(pNode cur) //分裂函數
{
pNode newNode = new Node;
int mid = (cur->size) / 2;
size_t j = 0;
size_t i = mid + 1;
for (; i < cur->size; i++)
{
newNode->_keys[j] = cur->_keys[i];
newNode->_pSub[j] = cur->_pSub[i];
if (newNode->_pSub[j] != NULL)
newNode->_pSub[j]->_pParent = newNode;
newNode->size++;
j++;
}
//拷右孩子
newNode->_pSub[j] = cur->_pSub[i];
if (cur->_pSub[i] != NULL)
newNode->_pSub[j]->_pParent = newNode;
return newNode;
}
void _InsertKey(pNode cur,const K value,pNode sub) //插入一個孩子和一個關鍵碼
{
int end = cur->size - 1;
while (end >= 0)
{
if (cur->_keys[end] > value)
{
cur->_keys[end + 1] = cur->_keys[end];
cur->_pSub[end + 2] = cur->_pSub[end + 1];
end--;
}
else
break;
}
//當end<0 或 value > cur->keys[end]
cur->_keys[end + 1] = value;
cur->_pSub[end + 2] = sub;
if (sub != NULL)
sub->_pParent = cur;
cur->size++;
}
pair<pNode,int> _Find(K value)
{
pNode ret = pRoot;
pNode parent = NULL;
while (ret)
{
size_t i = 0;
while (i < ret->size)
{
//在當前位置的左樹
if (value < (ret->_keys[i]))
break;
else if (value>(ret->_keys[i]))
{
i++;
}
else //有相同關鍵碼
return make_pair(ret, i);
}
//在左樹或沒找到
parent = ret;
ret = ret->_pSub[i];
}
return make_pair(parent,-1);
}
void _InOrder(pNode _pRoot) //中序遍歷
{
if (_pRoot == NULL)
return;
pNode cur = _pRoot;
size_t i = 0;
for (i = 0; i < cur->size; i++)
{
_InOrder(cur->_pSub[i]);
cout << cur->_keys[i] << " ";
}
_InOrder(cur->_pSub[i]);
}
private:
pNode pRoot;
};
void test()
{
int arr[] = { 53, 75, 139, 49, 145, 36, 101 };
int size = sizeof(arr) / sizeof(arr[0]);
BTree<int> bt;
for (int i = 0; i < size; i++)
{
bt.BTree_Insert(arr[i]);
}
bt.InOrder();
}
【test.cpp】
#include"BTree.h"
int main()
{
test();
system("pause");
return 0;
}