B-树基本操作的实现 利用C++模板类封装

程序还未经过严格的测试,不能保证完全正确,仅作为学习参考,同时如果遇见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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章