B樹
B樹(B-tree)是一種樹狀數據結構,它能夠存儲數據、對其進行排序並允許以O(log n)的時間複雜度運行進行查找、順序讀取、插入和刪除的數據結構。B樹,概括來說是一個節點可以擁有多於2個子節點的二叉查找樹。與自平衡二叉查找樹不同,B-樹爲系統最優化大塊數據的讀和寫操作。B-tree算法減少定位記錄時所經歷的中間過程,從而加快存取速度。普遍運用在數據庫和文件系統。
B樹是二叉搜索樹:
1.所有非葉子結點至多擁有兩個兒子(Left和Right);
2.所有結點存儲一個關鍵字
3.非葉子結點的左指針指向小於其關鍵字的子樹,右指針指向大於其關鍵字的子樹;
B樹的搜索,從根結點開始,如果查詢的關鍵字與結點的關鍵字相等,那麼就命中;如果查詢關鍵字比結點關鍵字小,就進入左兒子;如果比結點關鍵字大,就進入右兒子;如果左兒子或右兒子的指針爲空,則報告找不到相應的關鍵字;如果B樹的所有非葉子結點的左右子樹的結點數目均保持差不多(平衡),那麼B樹的搜索性能逼近二分查找;但它比連續內存空間的二分查找的優點是,改變B樹結構(插入與刪除結點)不需要移動大段的內存數據,甚至通常是常數開銷;但B樹在經過多次插入與刪除後,有可能導致不同的結構,也就是“平衡問題”。
B-樹(是“B樹”不是“B減樹”)
一棵M階(M>2)的B樹,是一棵平衡的M路平衡搜索樹,可以是空樹或者滿足一下性質:
1. 根節點至少有兩個孩子
2. 每個非根節點有[「M/2」,M]個孩子(孩子個數的區間)
3. 每個非根節點有[ 「M/2」,M-1]個關鍵字,並且以升序排列(關鍵字個數的區間)
4. key[i]和key[i+1]之間的孩子節點的值介於key[i]、key[i+1]之間
5. 所有的葉子節點都在同一層
ps:「M/2」是向上取整
B-樹的搜索,從根結點開始,對結點內的關鍵字(有序)序列進行二分查找,如果命中則結束,否則進入查詢關鍵字所屬範圍的兒子結點;重複,直到所對應的兒子指針爲空,或已經是葉子結點。
如:(M=3)
以B-樹的性能總是等價於二分查找(與M值無關),也就沒有B樹平衡的問題;由於M/2的限制,在插入結點時,如果結點已滿,需要將結點分裂爲兩個各佔M/2的結點;刪除結點時,需將兩個不足M/2的兄弟結點合併。
下面簡單說明分裂:
#pragma once
//3階B樹
template<class K, int M = 3>
struct BTreeNode
{
K _keys[M];//存儲有M-1個key,多一個是爲了方便分裂
BTreeNode<K, M>* _subs[M + 1];//存儲有M個subs,多一個是爲了方便分裂
BTreeNode<K, M>* _parent;
size_t _size;//數組中存在的有效關鍵字的個數
BTreeNode()
:_parent(NULL)
, _size(0)
{
int i = 0;
for (; i <= M; ++i)
{
_keys[i] = 0;
_subs[i] = NULL;
}
_keys[i] = 0;
}
};
template<class K,class V>//結構體實現K,V形式
struct Pair
{
K _first;
V _second;
Pair(const K& key = k(), const V& value = V())
:_first(key)
, _second(value)
{}
};
template<class K, int M = 3>
class BTree
{
typedef BTreeNode<K, M> Node;
public:
BTree()
:_root(NULL)
{}
bool Insert(const K& key)//插入節點
{
if (_root == NULL)
{
_root = new Node;
_root->_size++;
_root->_keys[0] = key;
return true;
}
Pair<Node*, int> ret= Find(key);
if (ret._second != -1)//判斷key是否已經存在
return false;
//在節點cur中插入key和sub
Node* cur = ret._first;
K insertkey = key;
Node* sub = NULL;
while (1)
{
_InsertKey(cur, insertkey, sub);
if (cur->_size < M)
return true;
//插入數據後,節點關鍵字個數大於M-1,需分裂節點,拷貝右半部分
int mid = (cur->_size - 1) / 2;//找到中間值,進行上移
int index = 0;
Node* tmp = new Node;//tmp分裂出來的右半部分,左半部分在cur中
//拷貝key和subs
for (size_t i = mid + 1; i < cur->_size; ++i)
{
//拷貝key
tmp->_keys[index++] = tmp->_keys[i];
tmp->_size++;
//拷貝subs
tmp->_subs[index++] = cur->_subs[i];
if (cur->_subs[i])
cur->_subs[i]->_parent = tmp;
}
cur->_size = (cur->_size - 1) / 2;//更新cur(分裂後的左半部分)大小
if (cur->_parent == NULL)//插入分裂後上移的元素
{
_root = new Node;
_root->_keys[0] = cur->_keys[mid];
_root->_size = 1;
_root->_subs[0] = cur;
_root->_subs[1] = tmp;
cur->_parent = _root;
tmp->_parent = _root;
break;
}
else
{
insertkey = cur->_keys[mid];
sub = tmp;
cur = cur->_parent;//上移
}
}
return true;
}
Pair<Node*, int> Find(const K& key) //查找key,返回節點及對應節點中數組下標
{
Node* parent = NULL;
Node* cur = _root;
while (cur)
{
size_t index = 0;
while (index < cur->_size)//遍歷整個節點關鍵字
{
if (key == cur->_keys[index])
return Pair<Node*, int>(cur, index);
else if (key > cur->_keys[index])
index++;
else//小於_key[index] --> 結束循環,在_key[index]所在節點查找
break;
}
parent = cur;
cur = cur->_subs[index];
}
return Pair<Node*, int>(parent, -1);//沒有找到,注意返回cur的父結點和-1
}
void InOrder()//中序遍歷輸出
{
_InOrder(_root);
}
private:
void _InsertKey(Node* cur,const K& key, Node* sub)//插入key值
{
int index = cur->_size - 1;//從後向前比較移位
while (index >= 0 && key < cur->_keys[index])//後面的數據(包括_sub[])向後移
{
cur->_keys[index + 1] = cur->_keys[index];
cur->_subs[index + 2] = cur->_subs[index + 1];//畫圖分析,_subs[]移動兩位
--index;
}
cur->_keys[index + 1] = key;
cur->_subs[index + 2] = sub;
if (sub)
sub->_parent = cur;
++cur->_size;
}
void _InOrder(Node* root)
{
if (root == NULL)
{
return;
}
for (int i = 0; i < _root->_size; ++i)
{
_InOrder(root->_subs[i]);
cout << root->_keys[i] << " ";
}
}
protected:
Node* _root;
};
void BTreeTest()
{
int a[] = { 53, 75, 139, 49, 145, 36, 101 };
BTree<int, 3> bt;
for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
{
bt.Insert(a[i]);
}
bt.InOrder();
}
B+樹
B+樹是B-樹的變體,也是一種多路搜索樹:
1.其定義基本與B-樹同,除了以下幾點不同。
2.非葉子結點的子樹指針與關鍵字個數相同;
3.非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-樹是開區間);
5.爲所有葉子結點增加一個鏈指針;
6.所有關鍵字都在葉子結點出現;
B+的搜索與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹可以在非葉子結點命中),其性能也等價於在關鍵字全集做一次二分查找。
B*樹
B*樹是B+樹的變體,在B+樹的非根和非葉子結點再增加指向兄弟的指針。
小結
●B樹:二叉樹,每個結點只存儲一個關鍵字,等於則命中,小於走左結點,大於走右結點;
●B-樹:多路搜索樹,每個結點存儲M/2到M個關鍵字,非葉子結點存儲指向關鍵字範圍的子結點;所有關鍵字在整顆樹中出現,且只出現一次,非葉子結點可以命中;
●B+樹:在B-樹基礎上,爲葉子結點增加鏈表指針,所有關鍵字都在葉子結點中出現,非葉子結點作爲葉子結點的索引;B+樹總是到葉子結點才命中;
●B*樹:在B+樹基礎上,爲非葉子結點也增加鏈表指針,將結點的最低利用率從1/2提高到2/3;