B树插入删除实现(c++)

B树的性质


1、树的每个节点最多有M个子树。

2、根节点至少有两个子树(除非它是叶子节点)。

3、除根节点之外的所有非叶子节点至少有M/2个子树(向上取整)。

4、所有非叶子节点包含keycount, childs[0],keys[1],childs[1],keys[2]........keys[keycount],childs[keycount]。

也就是所有非叶子节点的子树在key值的夹缝之间。key值的数量始终是子树数量 - 1(除叶子节点,叶子没有子树)。

M为树的阶数,keycount为节点key值数目,keys为键值数组,childs为子树数组。

通过性质可知

(M + 1) / 2 - 1 <= keycount <= M - 1

插入操作


1、其实每次的插入操作都会将key值插入到叶子节点上(key值是从小到大的,找到合适的位置插入)。

2、将节点插入后判断是否满足keycount <= M - 1。

2.1、若满足插入成功。

2.2、不满足,将节点中中值key取出,添加到父节点上(假设pos位置),然后将节点分裂成左右节点,父节点pos位置的key值的左子树分支childs[pos - 1]指向分裂后的左节点,右子树分支childs[pos]指向分裂后的右节点。然后由于父节点新添加了一个节点,所以很有可能父节点不满足,B树的性质了,那么开始调整父节点,重复过程。(在此过程中可能会使树的高度增加,创建新的根节点)。

这里有一篇文章,它里面有一个动态的图描述整个插入过程,需要的可以看一下:

https://www.cnblogs.com/vincently/p/4526560.html

删除操作


1、删除的key值在非叶子节点上,可以在节点的key位置的左子树中寻找最右节点,用最右几点中的最大key值替换,或在右子树中寻找最左节点,用最小key值替换,然后就会将问题转化为对叶子节点中的key值删除问题。

2、删除节点在叶子上。

2.1、删除key值后,仍然满足keycount >= (M + 1) / 2 - 1的条件,则删除成功。

2.2、删除后不满足条件。

2.2.1、如果该节点的左右兄弟节点有“多余的”key值(keycount > (M + 1) / 2 - 1),则可以向该兄弟借一个key值,如果想左兄弟借,就借它的最大key值,如果向右兄弟借,就借它的最小key值,同时别忘了要携带兄弟的childs[0]或childs[keycount](左兄弟就是最后的childs[keycount],右兄弟就是第一个childs[0])。在借的过程中,会把该节点的key、父节点对应位置的key和兄弟节点借的key值做一个联动。该节点获得父亲key,父节点获得兄弟借的key。(同时该节点也要获得兄弟携带的child子树)。调整完成后,删除成功。

2.2.2、如果“借不到”key值,只能合并了。从左右兄弟中选一个兄弟,再从父节点中拿下一个key值,将这些合并成一个节点(合并过程中,别忘了子树的移动)。mergedNode的父节点指向当前节点的父亲(也就是合并后的节点和当前节点,在同一层),父节点也要找到mergedNode在其childs中的适当位置,然后反指(parent->childs[pos] = mergedNode)。合并成功后,释放当前节点和兄弟节点,合并完成了,他们就没用了。因为从父节点拿下一个key所以要判断,mergedNode->parent是否满足B树性质(keycount >= (M + 1)/2 - 1),若满足删除成功。若不满足则以父节点为开始继续调整(向兄弟借 或者 合并)。

在调整过程中如果调整的节点的父节点为空,说明到了根节点,那么就不再继续调整了。但需要下面的判断。

当根节点keycount ==0 时,若此时的根节点为叶子节点,直接删除,根节点赋空,B树空了。

若不是叶子节点则将childs[0]置为新的root节点。

这里还有一篇文章,提供了图形的转换,脑袋里有图就更容易想通、

https://www.cnblogs.com/nullzx/p/8729425.html

实现


看了一些文章,实现各种各样,下面是我根据B树性质和插入删除操作的流程实现的,如果有错误之处,希望大家不吝赐教~


#ifndef _B_TREE_H
#define _B_TREE_H
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
/*
1.树的每个节点最多有M个子树
2.根节点至少有两个子树(如果不是叶子节点)
3.除根节点之外的所有非叶子节点,至少有M/2个子树(向上取整)
4.所有非叶子节点包括keycount,child[0],key[1],child[1],key[2]..key[keycount],child[keycount]
keycount为该节点包含的关键字数量
key为关键字
child为子树,子树插在各个关键字之间,所以比key多一个
*/
class MyBTree
{
private:
	enum {M=5,KEY_MIN=(M + 1)/2-1,KEY_MAX=M-1,KEY_START=1,CHILD_MIN=(M + 1)/2,CHILD_MAX=M};
	typedef struct Node
	{
		bool	leaf;					//是否为叶子节点
		int		keycount;				//键值个数,子树的个数为keycount + 1
		Node*	parent;					//父节点指针
		int		keys[M + 1];			//键值/数组是从1...M的
		Node*	childs[M + 1];			//子树指针0...M
		Node(Node*p = 0,bool flag = true):leaf(flag),keycount(0),parent(p){
		}
	}BTree;
	typedef struct Result			//查找插入节点信息
	{
		Node*	node;				//要将key值插入的节点
		int		keypos;				//要插入在node中的位置
		Result():node(0),keypos(1){}
	}SearchInsertResult,SearchEarseResult;
	BTree*		root;
	bool		release;
public:
	MyBTree():root(0),release(false){}
	~MyBTree(){
		if(this->release == false)
			this->destroy();
	}
private:
	int			find_pos(Node* node,int key);	//查找一个节点上key值合适插入的位置
	bool		find_node(int key,SearchInsertResult* searchres);	//寻找插入位置信息,返回false说明没有根节点
	bool		_insert(Node* node,int key,int keypos,Node* child = 0);		//在一个节点的keys中添加键值
	bool		split(Node* node,Node*& child);
	void		_destroy(Node* node);
	bool		find_earse(int key,SearchEarseResult* eraseres);	//返回false查找不存在,
	bool		get_ltree_rnode(Node* start,int keypos,Node*& res);	//得到keypos位置左子树最右节点
	int			get_childpos(Node* parent,Node* child);
	Node*		combine(Node* parent,Node* brother,Node* node,int parentKeyPos);	//返回合并之后的节点
public:
	bool		insert(int key);
	void		layer_print();
	bool		destroy();
	bool		earse(int key);
};

int		MyBTree::find_pos(Node* node,int key)
{
	if(node == nullptr)
		return -1;
	int pos = 1;
	for(;pos <= node->keycount && node->keys[pos] < key; pos++);
	return pos;
}

bool	MyBTree::find_node(int key,SearchInsertResult* searchres)
{
	if(root == nullptr){
		return false;
	}
	int pos = 0;
	BTree*	ptmp = root;
	while(!ptmp->leaf)
	{
		pos = this->find_pos(ptmp,key);
		ptmp = ptmp->childs[pos - 1];
	}
	pos = this->find_pos(ptmp,key);
	if(searchres){
		searchres->node = ptmp;
		searchres->keypos = pos;
	}
	return true;
}

bool	MyBTree::_insert(Node* node,int key,int keypos,Node* child)
{
	if(node == nullptr || keypos < 1)
		return false;
	for(int i = node->keycount + 1; i >= keypos; i--)	
	{
		node->keys[i] = node->keys[i - 1];
		node->childs[i] = node->childs[i - 1];
	}
	node->keys[keypos] = key;
	node->childs[keypos - 1] = child;
	if(child)
		child->parent = node;
	return true;
}

bool	MyBTree::split(Node* node,Node*& child)
{
	int pos = (M + 1) / 2;
	Node* left = new Node(node->parent,node->leaf);	//分裂出来的节点的父节点和叶子性与分裂节点相同
	child = left;
	left->childs[0] = node->childs[0];
	if(left->childs[0])
		left->childs[0]->parent = left;
	for(int i = 1; i < pos; i++)
	{
		left->keys[i] = node->keys[i];
		left->childs[i] = node->childs[i];	
		if(left->childs[i])
			left->childs[i]->parent = left;		//别忘了反指
		++left->keycount;
	}
	node->childs[0] = node->childs[pos];
	for(int i = pos + 1; i < M + 1; i++)
	{
		node->childs[i - pos] = node->childs[i];
		node->keys[i - pos] = node->keys[i];
	}
	node->keycount = node->keycount - pos;

	return true;		
}

/*
插入操作
1.每次的插入操作其实都是将键值插入到叶子节点中(key值合适的位置)
	1.1.若插入后该叶子节点的keycount 不满足 keycount <= M - 1
		将该节点key的中值取出,添加到父节点上的pos位置,然后将该
		节点分裂成左右两个节点,pos位置的key的左分支子树childs[pos - 1]
		为分裂后的左节点,右分支子树为childs[pos]为分裂后的右节点,
		因为父节点多了一个节点,所以要判断父节点是否满足条件。重复。直到满足条件。
		(此过程中,树的高度可能增加,生成新的根节点)
	1.2.若满足条件插入成功

*/

bool	MyBTree::insert(int key)
{
	SearchInsertResult res;
	if(this->find_node(key,&res) == false){
		root = new Node;
		res.node = root;
		res.keypos = 1;
	}
	Node* node = res.node;
	int   pos = res.keypos;
	Node* child = nullptr;
	int	mid = key;
	while(node)
	{
		this->_insert(node,mid,pos,child);
		++node->keycount;	
		if(node->keycount > KEY_MAX){//过大,分裂
			mid = node->keys[(M + 1) / 2];
			this->split(node,child);	//分裂child为返回的新创建的节点
			if(node->parent == nullptr){//说明到了根节点
				Node* ptmp = new Node(nullptr,false);		//因为下层分裂,这里不是叶子
				ptmp->keys[KEY_START] = mid;
				ptmp->childs[0] = child;
				ptmp->childs[1] = node;
				child->parent = ptmp;
				node->parent = ptmp;
				++ptmp->keycount;
				this->root = ptmp;		//重设根节点
				break;
			}else{
				node = node->parent;
				pos = this->find_pos(node,mid);
			}
		}else{
			break;
		}
	}
	return true;
}

void	MyBTree::layer_print()	//层序遍历
{
	if(this->root)
	{
		queue<Node*> Q;
		Q.push(root);
		while(!Q.empty())
		{
			auto ptmp = Q.front();
			Q.pop();
			for(int i = KEY_START; i <= ptmp->keycount; i++)
				cout<<ptmp->keys[i]<<" ";
			for(int i = 0; i <= ptmp->keycount && ptmp->keycount > 0 && !ptmp->leaf; i++)
				Q.push(ptmp->childs[i]);
		}
		cout<<endl;
	}
}

void	MyBTree::_destroy(Node* node)
{
	if(node == nullptr)
		return;
	if(node->leaf == false)
	for(int i = node->keycount; i >= 0; i--)
	{
		_destroy(node->childs[i]);
	}
	delete node;
	node = nullptr;
}

bool	MyBTree::destroy()
{
	if(this->release == false)
	{
		this->_destroy(this->root);
		this->release = true;
	}
	return true;
}
/*
删除操作
1.要删除的key值在非叶子节点上,使用该节点的子树节点中的child_key值填充,转到步骤2
2.要删除的key值在叶子节点上,如果不满足B树性质((M+1)/2 - 1 <= keycount <= M - 1),调整
	2.1	如果该节点的左右兄弟节点的keycount > (M + 1)/2 - 1,说明可以向他“借一个”
		将父节点中的key值下移一个到该节点中,然后后兄弟节点key值填充一个到父节点中。
	2.2	如果该节点的左右兄弟都不满足keycount > (M + 1)/2 - 1,说明兄弟节点的key数量
		刚刚好够它自己用,此时不能借了,拿下一个父节点中的key值与该节点和兄弟节点的
		所有key值合并,合成一个新节点。新节点指向父节点。如果因为从父节点上拿下一个key值
		而导致父节点不满足keycount >= (M + 1)/2 - 1的条件,则调整父节点。
*/
bool	MyBTree::find_earse(int key,SearchEarseResult* eraseres)
{
	Node* ptmp = root;
	int pos = 1;
	while(1)
	{
		for(pos = 1; pos <= ptmp->keycount && ptmp->keys[pos] < key; pos++);
		if(pos > ptmp->keycount || ptmp->keys[pos] > key)
		{//如果当前节点的key值都小于该key值,或key值在keys[pos - 1],keys[pos]之间
			if(ptmp->leaf){//不存在key值
				return false;
			}
			ptmp = ptmp->childs[pos - 1];
		}else if(ptmp->keys[pos] == key){//找到
			eraseres->node = ptmp;
			eraseres->keypos = pos;
			return true;
		}
	}
	return true;
}
//得到左子树的最右节点
bool	MyBTree::get_ltree_rnode(Node* start,int keypos,Node*& res)
{
	if(start == nullptr)
		return false;
	start = start->childs[keypos - 1];
	while(!start->leaf)
	{
		start = start->childs[start->keycount];
	}
	res = start;
	return true;
}

//得到child在parent中的位置
int		MyBTree::get_childpos(Node* parent,Node* child)
{
	for(int i = 0; i <= CHILD_MAX;i++)
		if(child == parent->childs[i])
			return i;
	return -1;
}

MyBTree::Node*	MyBTree::combine(Node* parent,Node* brother,Node* node,int parentKeyPos)
{
	int key = parent->keys[parentKeyPos];
	for(int i = parentKeyPos; i < parent->keycount; i++){
		parent->keys[i] = parent->keys[i + 1];
		parent->childs[i] = parent->childs[i + 1];
	}
	--parent->keycount;

	//找到比key值小的节点直接将key值添加到该节点上,方便后面操作
	if(node->keycount == 0 || key >= node->keys[node->keycount]){
		//说明key值的排序是node key brother也就是node在brother左侧
		node->keys[node->keycount + 1] = key;
		node->childs[node->keycount + 1] = brother->childs[0];	//注意子树也要移动
		++node->keycount;
	}else{
		brother->keys[brother->keycount + 1] = key;
		brother->childs[brother->keycount + 1] = node->childs[0];
		++brother->keycount;
	}

	Node*	mergedNode = new Node(parent,node->leaf);
	if(node->keys[1] > brother->keys[brother->keycount]){
		mergedNode->childs[0] = brother->childs[0];
		for(int i = 1; i <= brother->keycount; i++){
			mergedNode->keys[i] = brother->keys[i];
			mergedNode->childs[i] = brother->childs[i];
			if(!mergedNode->leaf)		//不要忘反指
				mergedNode->childs[i]->parent = mergedNode;
		}
		for(int i = brother->keycount + 1; i <= brother->keycount + node->keycount; i++){
			mergedNode->keys[i] = node->keys[i - brother->keycount];
			mergedNode->childs[i] = node->childs[i - brother->keycount];
			if(!mergedNode->leaf)		//不要忘反指
				mergedNode->childs[i]->parent = mergedNode;
		}
	}else{
		mergedNode->childs[0] = node->childs[0];
		for(int i = 1; i <= node->keycount; i++){
			mergedNode->keys[i] = node->keys[i];
			mergedNode->childs[i] = node->childs[i];
			if(!mergedNode->leaf)		//不要忘反指
				mergedNode->childs[i]->parent = mergedNode;
		}
		for(int i = node->keycount + 1; i <= node->keycount + brother->keycount; i++){
			mergedNode->keys[i] = brother->keys[i - node->keycount];
			mergedNode->childs[i] = brother->childs[i - node->keycount];
			if(!mergedNode->leaf)		//不要忘反指
				mergedNode->childs[i]->parent = mergedNode;
		}
	}
	mergedNode->keycount = node->keycount + brother->keycount;
	parent->childs[parentKeyPos - 1] = mergedNode;

	delete node;
	delete brother;
	return mergedNode;
}

bool	MyBTree::earse(int key)
{
	if(this->root == nullptr)
		return false;
	SearchEarseResult eraseres;
	if(false == this->find_earse(key,&eraseres))
		return false;
	Node* node = eraseres.node;
	int pos = eraseres.keypos;
	if(!node->leaf)
	{//非叶子节点
		Node* replace = nullptr;	//替换key值的节点
		this->get_ltree_rnode(node,pos,replace);
		node->keys[pos] = replace->keys[replace->keycount];
		node = replace;		
		--replace->keycount;
	}else{
		for(int i = pos; i < node->keycount; i++)
		{
			node->keys[i] = node->keys[i + 1];
			//叶子节点没有子树,就不用移动了
		}
		--node->keycount;
	}
	while(1)
	{
		if(node->parent == nullptr)
		{//根节点
			if(node->keycount == 0){
				if(!node->leaf){//不是叶子节点说明有child,更改根节点
					root = node->childs[0];
					root->parent = nullptr;
				}
				else
					root = nullptr;
				delete node;
			}
			return true;
		}
		

		if(node->keycount < KEY_MIN)
		{//不满足keycount => (M + 1)/2 - 1
			int childpos = this->get_childpos(node->parent,node);	//得到node在node->parent childs中的位置
			//先借
			auto parent = node->parent;
			if(childpos > 0 && parent->childs[childpos - 1]->keycount > KEY_MIN)
			{//可以向左兄弟借,借左兄弟最后一个key值也携带最后一个child,
				//父亲节点key给当前,添加到第一个位置,这个key给父亲节点,携带的child给当前,第一个位置。
				auto left = parent->childs[childpos - 1];
				for(int i = node->keycount + 1; i >= 1; i--){
					node->keys[i] = node->keys[i - 1];
					node->childs[i] = node->childs[i - 1];
				}
				node->keys[1] = parent->keys[childpos];
				++node->keycount;

				node->childs[0] = left->childs[left->keycount];	//携带的child
				node->childs[0]->parent = node;

				parent->keys[childpos] = left->keys[left->keycount];
				--left->keycount;
				return true;
			}else if(childpos < parent->keycount && parent->childs[childpos + 1]->keycount > KEY_MIN)
			{//可以向右兄弟借,借右兄弟的第一个key值,也携带第一个child
				auto right = parent->childs[childpos + 1];
				node->keys[node->keycount + 1] = parent->keys[childpos + 1];
				++node->keycount;
				parent->keys[childpos + 1] = right->keys[1];

				
				node->childs[node->keycount] = right->childs[0];//携带的child
				node->childs[0]->parent = node;

				for(int i = 0; i < right->keycount; i++){
					right->keys[i] = right->keys[i + 1];
					right->childs[i] = right->childs[i + 1];
				}

				--right->keycount;
				return true;
			}else
			{//没借到,向父节点要,再一起与左或右节点合并
				Node* mergedNode = nullptr;
				if(childpos > 0)
				{//有左兄弟
					auto left = parent->childs[childpos - 1];
					mergedNode = this->combine(parent,left,node,childpos);
				}else if(childpos < parent->keycount)
				{//有右兄弟
					auto right = parent->childs[childpos + 1];
					mergedNode = this->combine(parent,right,node,childpos + 1);
				}
				node = mergedNode->parent;	//上溯
			}
		}else{//满足keycount => (M + 1)/2 - 1
			return true;
		}
	}
	return true;
}
#endif

为了更加方便操作,程序中就以int类型为键值类型。

改变代码中的M的值可以改变树的阶数。

在看代码时,主要看insert,erase两个函数,理清流程后,再看具体的实现。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值