B树

B树:严格来讲,B树并非BST,但从逻辑上讲,仍然等效于BST。

引入B树的目的:弥补不同存储设备访问速度的巨大差异,实现高效的I/O。普遍应用在数据库和文件系统中。B树结构非常适宜于在相对更小的内存中,实现对大规模数据的高效操作。

特点:搜索每下降一层,都以“大节点”为单位从外村读取一组(而不是单个)关键码。更为重要的是,这组关键码在逻辑上和物理上都彼此相邻,故可以批量方式从外村中一次性读取,且所需时间与读取单个关键码几乎一样。

注意:1)B树新建节点发生于插入操作的时候,当要插入的节点已经满了,需要分裂出一个新的节点,此时增加了一个新节点。而删除大节点发生在删除一个节点,且当前大节点中元素个数小于要求,且左右子树也小于要求,此时合并当前节点和其左子树,或右子树,用其父节点中的一个节点来连接;另一种情况是从根节点借了一个节点,此时根节点已经没有节点了,删除根节点。

2)B树高度永远保持平衡,即对于大节点来说,B树的高度永远是平衡的,任一节点的左子树和右子树的高度平衡,这是因为B树的高度的增加完全是由于根节点的分裂而导致的,而高度的减少,也是由于根节点的删除导致的,操作都在根节点,因此永远是平衡的,但是不用作普通平衡搜索树的原因是保持其平衡的代价太高了,合并和分裂一个节点的复杂度高。

3)阶数表示B数节点可以拥有的最大的孩子数,m阶B树表示B树中的“大节点”除了根节点外,其它“大节点”必须至少有[m/2]([]表示向上取整)个子节点,最多有m个子节点。根最少有2个子节点,最多有m个子节点。

4)每个节点的关键字个数有个上界和下界。用一个被称为B树的最小度数的固定整数t来表示这个界。如果用度来度量B树,则度为t的B树,根节点最少为1,最多为2t-1,其它节点的度最少为t-1,最多为2t-1个关键字。孩子数根节点最少为2,最多为2t,其它节点最少为t,最多为2t。

下面的代码以3)定义,且m=5。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

//B树中大节点的定义
struct BTNode
{
	vector<int> key;
	BTNode *parent;
	vector<BTNode *> child;
	BTNode()
	{
		parent=nullptr;
	}
	BTNode(int e,BTNode * lc=nullptr,BTNode * rc=nullptr)
	{
		parent=nullptr;
		key.insert(key.begin(),e);
		child.insert(child.begin(),lc);
		child.insert(child.begin()+1,rc);
		if(lc)//只有当左孩子不是空,才能给它赋值父节点
			lc->parent=this;
		if(rc)//只有当右孩子不是空,才能给它赋值父节点
			rc->parent=this;
	}
	~BTNode()
	{cout<<"调用析构函数"<<endl;}
};

class BTree
{
private:
	int _size;
	int _order;//存放B树的阶数,即B树中节点最多能有几个分支,至少为3,当为2时是二叉树
	BTNode * _root;
	BTNode *_hot;//search接口最后访问的非空(除非树空)的节点位置
	void solveOverFlow(BTNode *);
	void solveUnderFlow(BTNode *);
	int find(BTNode *v,int e);
	void release(BTNode *root);
public:
	BTree(int order=3):_size(0),_order(order)
	{_root=new BTNode();}
	~BTree()
	{
		release(_root);
	}
	int order()const {return _order;}
	int size()const {return _size;}
	BTNode *root()const {return _root;}
	bool empty()const {return !_root;}
	BTNode * search(int e);
	bool insert(int e);
	bool remove(int e);
};

int BTree::find(BTNode *v,int e)
{
	int k=-1;
	for(int i=0;i<v->key.size();i++)
	{
		if(v->key[i]==e)
			return i;
		if(v->key[i]<e)
			k=i;
	}
	return k;
}

BTNode * BTree::search(int e)
{
	BTNode *v=_root;
	_hot=_root;//从根节点开始
	while(v)
	{
		int r=find(v,e);//先在本节点key中寻找
		if(r<0 && v->key.size()==0)
			break;
		if(r>=0 && (v->key[r]==e))
			return v;
		_hot=v;
		v=v->child[r+1];//如果没有找到则深入到下一层孩子节点中
	}
	return nullptr;
}

bool BTree::insert(int e)
{
	BTNode *v=search(e);
	if(v)
		return false;
	int r=1+find(_hot,e);
	_hot->key.insert(_hot->key.begin()+r,e);
	//对于每一个B树来说,只会调用一次,即在第一次插入节点的时候。其它时候增加的BTNode都是在solveOverFlow函数中
	if(r==0 && _hot->key.size()==1)//只有在第一个节点的时候插入时,才需要同时插入两个NULL的孩子节点
		_hot->child.insert(_hot->child.begin()+r,nullptr);//其它时候即使要插入的e应该在key[0]由于孩子为NULL,
														  //因此插入到第0个和插入到第一个是一样的。
	_hot->child.insert(_hot->child.begin()+r+1,nullptr);//此时插入的节点的兄弟的孩子也都是空的,即插入到叶节点
	_size++;
	solveOverFlow(_hot);
	return true;
}

void BTree::solveOverFlow(BTNode *v)
{
	if(_order>=v->child.size())
		return ;//此时不需要进行上溢处理
	int s=_order>>1;
	BTNode *u=new BTNode();
	for(int i=0;i<_order-s-1;i++)
	{//如下是把u指向了v的右侧的节点
		u->child.insert(u->child.begin()+i,*(v->child.begin()+s+1));//这里从s+1开始,是要保证左孩子比右孩子节点不少,
		v->child.erase(v->child.begin()+s+1);//使左孩子能够提出一个p到父节点
		u->key.insert(u->key.begin()+i,*(v->key.begin()+s+1));
		v->key.erase(v->key.begin()+s+1);
	}
	u->child.push_back(v->child.back());//最后一个孩子单独放,因为key比child少一个节点
	v->child.pop_back();
	if(u->child[0])//统一将孩子的父节点指向u
		for(int i=0;i<u->child.size();i++)
			u->child[i]->parent=u;
	BTNode *p=v->parent;
	if(!p)//当前这个大节点是根节点
	{
		_root=p=new BTNode();//注意这里是唯一一处增加节点的地方!!!
		p->child.push_back(v);//插入孩子节点,值在下面插入
		v->parent=_root;
	}
	int r=1+find(p,v->key[0]);//p中指向v的指针
	p->key.insert(p->key.begin()+r,v->key.back());//轴点的关键码上升
	v->key.pop_back();//将关键码从左子树中删除,此时左子树已经是纯粹的左子树了
	p->child.insert(p->child.begin()+r+1,u);
	u->parent=p;
	solveOverFlow(p);
}

void BTree::release(BTNode *root)
{//递归地进行析构
	if(root==nullptr)
		return ;
	for(int i=0;i<root->child.size();i++)
		release(root->child[i]);
	delete root;
}

bool BTree::remove(int e)
{
	BTNode *v=search(e);
	if(!v)
		return false;//即没有要删除的关键字e
	int r=find(v,e);
	if(v->child[0])
	{
		BTNode *u=v->child[r+1];//在右子树中找到和e中序遍历相邻的元素
		while(u->child[0])
			u=u->child[0];
		v->key[r]=u->key[0];//交换关键码
		v=u;
		r=0;
	}
	v->key.erase(v->key.begin());//删除键值
	v->child.erase(v->child.begin());//删除孩子,这个孩子是空的,因为是叶子节点
	_size--;
	solveUnderFlow(v);
	return true;
}

void BTree::solveUnderFlow(BTNode *v)
{
	if(((_order+1)>>1)<=v->child.size())
		return ;//当前节点并未下溢,
	BTNode *p=v->parent;
	if(!p)
	{//当前的v是根节点,但是此时的根节点由于被下面节点拉先来一个关键字而成空的了,
	 //但是其孩子节点0还不是空的
		if(!v->key.size() && v->child[0])
		{
			_root=v->child[0];
			_root->parent=nullptr;
			delete v;
		}
		return ;
	}
	int r=0;
	while(p->child[r]!=v)//确定v是p的第r个孩子,此时p有可能不含关键码,因此不能用关键码来查找
		r++;
	if(r>0)//有左兄弟
	{
		BTNode *ls=p->child[r-1];
		if(((_order+1)>>1)<ls->key.size())//左兄弟足够胖,能够借出一个关键码
		{
			v->key.insert(v->key.begin(),p->key[r]);
			v->child.insert(v->child.begin(),ls->child.back());
			p->key[r]=ls->key.back();
			ls->key.pop_back();
			ls->child.pop_back();
			if(v->child[0])
				v->child[0]->parent=v;
			return ;
		}
	}
	if(r<p->child.size()-1)//即有右兄弟
	{
		BTNode *rs=p->child[r+1];
		if(((_order+1)>>1)<rs->key.size())
		{
			v->key.push_back(p->key[r]);
			v->child.push_back(rs->child.front());
			p->key[r]=rs->key.back();
			rs->key.erase(rs->key.begin());
			rs->child.erase(rs->child.begin());	
			if(v->child.back())
				v->child.back()->parent=v;
			return ;
		}
	}
	if(r>0)//和左兄弟合并
	{
		BTNode *ls=p->child[r-1];
		ls->key.push_back(p->key[r-1]);
		p->key.erase(p->key.begin()+r-1);
		p->child.erase(p->child.begin()+r);//把右边孩子给删除
		ls->child.push_back(v->child.front());//把第一个孩子过继给左兄弟
		v->child.erase(v->child.begin());
		if(ls->child.back())
			ls->child.back()->parent=ls;
		while(!v->key.empty())
		{
			ls->key.push_back(v->key.front());
			v->key.erase(v->key.begin());
			ls->child.push_back(v->child.front());
			v->child.erase(v->child.begin());
			if(ls->child.back())
				ls->child.back()->parent=ls;
		}
		delete v;//这里只是把v这个节点删除,而其孩子都过继给了其兄弟,因此不用调用release函数
	}
	else//与其右兄弟合并
	{
		BTNode *rs=p->child[r+1];
		rs->key.insert(rs->key.begin(),p->key[r]);
		p->key.erase(p->key.begin()+r);
		p->child.erase(p->child.begin()+r);//把左边子树给删除
		rs->child.insert(rs->child.begin(),v->child.back());
		v->child.pop_back();
		if(rs->child.front())
			rs->child.front()->parent=rs;
		while(!v->key.empty())
		{
			rs->key.insert(rs->key.begin(),v->key.back());
			v->key.pop_back();
			rs->child.insert(rs->child.begin(),v->child.back());
			v->child.pop_back();
			if(rs->child.front())
				rs->child.front()->parent=rs;
		}
		delete v;
	}
	solveUnderFlow(p);
	return ;
}



int main()
{
    BTree bt(5);
	bt.insert(1);
	bt.insert(3);
	bt.insert(2);
	bt.insert(4);
	bt.insert(5);
	bt.insert(6);
	bt.insert(7);
	for(int i=8;i<18;i++)
		bt.insert(i);

	for(int i=1;i<18;i++)
		bt.remove(i);

	system("pause");
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值