程序還未經過嚴格的測試,不能保證完全正確,僅作爲學習參考,同時如果遇見bug歡迎指出。(把B-樹當成集合用在codefoces上面提交了一題,AC了,感覺問題不大)
註釋感覺還比較詳細,我就不多bb了,直接上代碼
/*
項目名:B-樹
作者:龍卡
完成時間:2020/12/21 0:04
個人感受:手撕B-樹真的太難了,要想寫好還得在每一步把所有可能的情況全部考慮完,調試過程中遇到的最嚴重的bug就是par指針
的維護不到位,有很多情況沒有維護好,導致出了bug。本來以爲會讓做平衡二叉樹的實驗,結果直接上B-樹,只能說 “老趙,可真有你的”
*/
#include<iostream>
#include<algorithm>
#include<deque>
using namespace std;
const int M = 5;//B-樹的階
const int MAX_N = M - 1;//關鍵字個數最大值
const int MIN_N = (M >> 1) + (M & 1) - 1;//最小值
const int SIZE = M + 1 + 1;//key,pchild的真實容量,M+1是爲了下標,再+1是爲了處理插入的情況
const int MID = (M >> 1) + (M & 1);//m/2向上取整
template<typename T>
class BMTree;
template<typename T>
class result;
//#define private public//debug永遠滴神
template<class T>
class node {
private:
int n;//關鍵字的個數
T key[SIZE];
node<T>* pchild[SIZE];
node<T>* par;
node(int n, node<T>* par = NULL) :n(n), par(par) {
for (int i = 0; i < SIZE; i++)pchild[i] = NULL; }//我真沒有壓行的嫌疑[捂臉]
int insert_key(const T& value)//在key中插入value,返回插入的位置下標
{
int pos= upper_bound(key + 1, key + n + 1, value) - key;
for (int i = ++n; i > pos; i--)key[i] = key[i - 1],pchild[i]=pchild[i-1];
key[pos] = value;
return pos;
}
void delete_key(int pos)
{
if (pos<0 || pos>n)throw "delete_key Error:Index out of range.";
for (int i = pos; i <=n; i++)key[i] = key[i + 1],pchild[i]=pchild[i+1];//這裏小於等於n是爲了讓沒用的指針保持爲空,避免插入時候的隱患
n--;
}
node<T>* split()//自己作爲左兒子分裂,返回分裂後的右兒子指針
{
node<T>* lchild = this, * rchild = new node(M/2,this->par);
this->n = MID - 1;
for (int i = MID+1; i <= M; i++)
rchild->key[i-MID] = lchild->key[i];
for (int i = MID; i <= M; i++)
rchild->pchild[i-MID] = lchild->pchild[i];
return rchild;
}
bool too_big()const {
return n > MAX_N; }
bool rich()const {
return n > MIN_N; }//大於最小值說明可以刪掉一個,就比較富有的啦
bool too_small()const {
return n < MIN_N; }
static node<T>* merge(node<T>* a, node<T>* b,const T& value)//左a右b,順序很要緊
{
a->key[++(a->n)] = value;
a->pchild[a->n] = b->pchild[0];
for (int i = 1; i <= b->n; i++)a->key[i+a->n]=b->key[i], a->pchild[i+a->n]=b->pchild[i];
a->n += b->n;
delete b;
return a;
}
friend class BMTree<T>;
friend class result<T>;
};
template<typename T>
class result {
public:
bool tag;
int i;
node<T>* p;
deque<int> path;
result() {
}
result(node<T>* p, int i, bool tag,const deque<int> &path) :p(p), i(i), tag(tag),path(path) {
}
void show()
{
cout << "R";
for (deque<int>::iterator ite = path.begin(); ite != path.end(); ++ite)cout << *ite;
cout << ",n=" << p->n<< "(";
for (int i = 1; i <= p->n; i++)cout << "," + !(i - 1) << p->key[i];
cout << ")\n";
}
};
template<typename T>
class BMTree
{
private:
node<T>* head;
private:
BMTree(BMTree& b) {
}
public:
BMTree() :head(NULL) {
}
~BMTree(){
clear(head);}//刪除樹中的所有結點
private:
void refresh(node<T>* ptr){
for (int i = 0; i <= ptr->n&&ptr->pchild[i]; i++)ptr->pchild[i]->par = ptr;}
void clear(node<T>* ptr)
{
if (!ptr)return;
for (int i = 0; i <= ptr->n; i++)clear(ptr->pchild[i]);
delete ptr;
}
void split(node<T>* ptr)//從p結點開始分裂
{
if (ptr->n <= MAX_N)return;//沒有超過需求
node<T>* par = ptr->par;
int i = 1;
if (!par)//沒有父親結點,說明當前是頭節點
{
par = new node<T>(1);
head = par;
ptr->par = par;
par->key[1] = ptr->key[MID];
}
else i = ptr->par->insert_key(ptr->key[MID]);
node<T>* rchild = ptr->split(), * lchild = ptr;
par->pchild[i - 1] = lchild;
par->pchild[i] = rchild;
refresh(rchild);//就這個bug可以de一天
split(ptr->par);
}
void check(node<T>* ptr,node<T> *last=NULL)//debug用的,不必在意
{
if (!ptr)return;
if (ptr->par != last)cout <<"Error 錯誤結點對應的首個關鍵字"<< ptr->key[1]<<'\n';
for (int i = 0; i <= ptr->n; i++)check(ptr->pchild[i],ptr);
}
void merge(node<T>* ptr)
{
check(head);
if (ptr->n >= MIN_N)return;
if (ptr == head)
{
if (ptr->n > 0)return; //頭節點指針個數可以少於MIN_N;
delete head;//結點被全部刪除完了
head = NULL;
return;
}
node<T>* par = ptr->par, *rbro = NULL, * lbro = NULL;
T* key = par->key;
int i = upper_bound(key + 1, key + par->n + 1, ptr->key[1]) - key-1;
if (i > 0)lbro = par->pchild[i - 1];
if (i < par->n)rbro = par->pchild[i + 1];
//這下面的很多父親結點沒有改變,導致錯誤(已被修復,不愧是我,✌( •`ω •^ )✌)
if (lbro && lbro->rich())
{
ptr->insert_key(par->key[i]);
ptr->pchild[1] = ptr->pchild[0];
ptr->pchild[0] = lbro->pchild[lbro->n],par->key[i]=lbro->key[lbro->n];//將左兄弟的最大值提上去
//刪除再插入可能會有指針問題,記得清空指針
lbro->delete_key(lbro->n);
refresh(ptr);
return;
}
if (rbro && rbro->rich())
{
ptr->insert_key(par->key[i+1]);
ptr->pchild[ptr->n] = rbro->pchild[0], par->key[i+1] = rbro->key[1];//將右兄弟的最小值提上去
rbro->pchild[0] = rbro->pchild[1];
rbro->delete_key(1);
refresh(ptr);
return;
}
//沒有富裕的情況,可能會改變頭節點指針哦.
node<T>* l = (rbro ? rbro : lbro),*r=ptr;
if (l == rbro)std::swap(l, r);
i = (rbro ? (i + 1):i);//i爲兩者中間夾的key的位置編號
ptr = node<T>::merge(l, r, par->key[i]);//merge了以後必須更新ptr,因爲原來的可能被釋放了
refresh(ptr);
if (par == head&&par->n==1)//頭節點只有一個關鍵字了,要將其刪除
{
delete head;
head = ptr;
head->par = NULL;
return;
}
par->delete_key(i);
par->pchild[i-1] = ptr;//把合併後的結點連上去
merge(par);
}
node<T>* min_key(node<T>* root)
{
if (root == NULL)throw "mid_key ERROR:從終端結點進入函數!";
if (root->pchild[0] == NULL)return root;
else return min_key(root->pchild[0]);
}
public:
result<T> find(const T& value)const
{
return find(value, head);
}
result<T> find(const T& value,node<T> *root)const//查找成功返回true
{
node<T>* ptr = root, * last = NULL;
deque<int> que;
bool found = false;
int i = -1;
while (ptr && !found)
{
T* key = ptr->key, n = ptr->n;
i = upper_bound(key + 1, key + n + 1, value) - key - 1;//找到大於value的第一個值的位置的前趨
if (i > 0 && key[i] == value)found = true;
else
{
last = ptr;
ptr = ptr->pchild[i];
que.push_back(i);
}
}
if (found)return result<T>(ptr, i, true,que);
else return result<T>(last, i, false,que);//沒找到返回應該插入的位置,即 key的i - i+1之間
}
bool insert(const T& value)//插入失敗說明已經存在
{
result<T> pos = find(value);
if (pos.tag)return false;
if (head == NULL)
{
head = new node<T>(1, NULL);
head->key[1] = value;
return true;
}
//因爲查找返回的位置一定是終端結點,所以才能直接插入value
node<T>* ptr = pos.p;
ptr->insert_key(value);
if (ptr->too_big())split(ptr);//需要分裂
return true;
}
void show_in(node<T> *ptr,deque<int> &num)
{
if (!ptr)return;
result<T> res;
res.path = num,res.p=ptr;
res.show();
for (int i = 0; i <= ptr->n; i++)
if (ptr->pchild[i])
{
num.push_back(i);
show_in(ptr->pchild[i],num);
num.pop_back();
}
}
bool erase(const T& value){
return erase(value, head);}
bool erase(const T& value,node<T> *root)//在root子樹中刪除
{
result<int> res = find(value,root);
if (!res.tag)return false;//B-樹裏面沒有對應關鍵字,刪除失敗
//刪除
//1.找到要刪除的關鍵字對應的結點
//2.若該節點爲終端結點,那麼直接刪除關鍵字,不是的話,就把關鍵字對應的右子樹的最小值拿過來,同時在右子樹刪除它
node<T>* ptr = res.p;
int i = res.i;
if (ptr->pchild[0] == NULL)
{
ptr->delete_key(i);//刪除的時候個數肯定不會超標,delete_key不會下標越界
merge(ptr);
}
else
{
node<T>* k = min_key(ptr->pchild[i]);//右子樹最小值
ptr->key[i] = k->key[1];
erase(k->key[1],ptr->pchild[i]);
}
return true;
}
void show(){
deque<int> que;show_in(head,que);}
};
int main()
{
//freopen("input.txt","r",stdin);
BMTree<int> tree;
int x;
while (true)
{
cout << "1.插入關鍵字 \n2.刪除關鍵字 \n3.查找關鍵字 \n4.層次遍歷輸出B-樹所有結點 \n5.結束程序\n";
cin >> x;
result<int> res;
switch (x)
{
case 1:
cout << "輸入要插入的關鍵字:";
cin >> x;
if (!tree.insert(x))cout << "插入失敗\n";
//tree.show();
cout << endl;
break;
case 2:
cout << "輸入要刪除的關鍵字:";
cin >> x;
cout << (tree.erase(x) ? "刪除成功!":"刪除失敗,結點不存在")<<endl;
break;
case 3:
cout << "輸入要查找的關鍵字:";
cin >> x;
res = tree.find(x);
if (!res.tag)
cout << "關鍵字不存在!\n";
else res.show();
break;
case 4:
tree.show();//深度遍歷,還需要改成寬度優先遍歷
break;
case 5:
return 0;
default:
break;
}
}
return 0;
}