算法导论-第18章-B树

一、定义

1、B树

B树是为磁盘或其它直接存取辅助存储设备而设计的一种平衡查找树,主要特点是降低磁盘I/O操作次数。
B树以自然的方式推广二叉查找树。
B树的分支因子由磁盘特性所决定。 

2、B数的数据结构

int n:当前存储在结点x中的关键字个数
key[N]:每个结点有n个关键字,以非降序存放
bool leaf;//TRUE:x是叶子;FALSE:x是内结点
node *child[N+1]:只有内部结点才有,叶结点没有。(孩子结点个数为当前结点关键字的个数+1)。指向其n+1个孩子的指针。child[1].key <= key[1] <= child[2].key……,即当前结点的第i个关键字不小于它的第i个孩子结点的所有关键字,不大于它的第i+1个孩子结点的所有关键字。

3.B树的特征

(1)只有内结点才有指向子女的指针,且child[1].key <= key[1] <= child[2].key……
(2)每个叶结点具有相同的深度,即树的高度h。
(3)分支因子(最小度数)t>=2
(4)每个 非根 结点至少有t-1个关键字,如果是内结点,至少有t个子女(根结点可以有一个关键字,也可以有多个,孩子结点个数=关键字个数+1)
(5)每个结点至多有2t-1个关键字,如果是内结点,至多有2t个子女。如果有2t个孩子,称该结点是满的。

4.B树上的操作

B-Tree-Search(x, k);查找关键字K,时间:O(t*logt n)。
B-Tree-Create(T);创建一棵空的B树
B-Tree-Split-Child(x,i,y);分裂B树中的满结点,把一个结点分成两个,主要用于后面的插入结点。O(t)。
B-Tree-Insert-Nonfull(x,k);在非满结点x中插入关键字k。
B-Tree-Insert(T,k);向以T为根结点的B树中插入关键字k。当T为满结点时,先调用 B-Tree-Split-Child(x,i,y)进行分裂,然后再调用上面的 B-Tree-Insert-Nonfull(x,k)进行插入。 O(t*logt n)。
B-Tree-Delete(T,k);删除关键字k, O(t*logt n)。

二、代码

B_Tree.h

#include <iostream>
using namespace std;

#define N 10
int t = 2;
//B树结点结构
struct node
{
	int n;//当前存储在结点x中的关键字数
	char key[N];//n个关键字,以非降序存放
	bool leaf;//TRUE:x是叶子;FALSE:x是内结点
	node *child[N+1];//指向其n+1个孩子的指针
	//构造函数
	node(int num, bool IsLeaf):n(num),leaf(IsLeaf){}
	//磁盘读写操作
	void Disk_Read(){}
	void Disk_Write(){}
};
//B树结构
class B_Tree
{
public:
	//指向根结点
	node *root;
	B_Tree():root(NULL){}

	//从以x为根结点的树中寻找关键字为k的结点,若找到,将结果存入y中,返回其是第几个关键字
	int B_Tree_Search(node *x, char k, node&y);
	//构造一棵带树结点的空树
	void B_Tree_Create();
	//分裂,把y分裂为两个结点,选择其中一个关键字插入到x中的第i个位置
	void B_Tree_Split_Child(node *x, int i, node *y);
	//将关键字k插入到一个未满的结点x中
	void B_Tree_Insert_Nonfull(node *x, char k);
	//向T中插入关键字k
	void B_Tree_Insert(char k);
	//删除T树中关键字为k的结点,由于是递归方法,当前处理的是x结点
	void B_Tree_Delete(node *x, char k);
	//按关键字从小到大输出结点
	void Print(node *n);
};

//从以x为根结点的树中寻找关键字为k的结点,若找到,将结果存入y中,返回其是第几个关键字
int B_Tree::B_Tree_Search(node *x, char k, node&y)
{
	int i = 1;
	//找到第一个关键字不大于k的i
	while(i < x->n && k > x->key[i])
		i++;
	//若key[i] = k,则找到了
	if(i <= x->n && k == x->key[i])
	{
		//将结果存入y中
		y = *x;
		//返回其是第几个关键字
		return i;
	}
	//若没找到,则返回空
	if(x->leaf)
	{
		//	&y = NULL;
		return 0;
	}
	//若还有子树可以找,则递归查找第i个子树
	x->child[i]->Disk_Read();
	return B_Tree_Search(x->child[i], k, y);
}
//构造一棵带树结点的空树
void B_Tree::B_Tree_Create()
{
	//生成一个根结点
	//初始时,根结点为叶子结点,根结点中没有关键字
	root = new node(0, true);
	root->Disk_Write();
}
//分裂,把满结点y分裂为两个结点,选择其中一个关键字插入到x中的第i个位置
void B_Tree::B_Tree_Split_Child(node *x, int i, node *y)
{
	int j;
	//生成一个新结点z
	//要把y分裂为y和z,前面的y除了n其他都不变,要为后面的z赋关键字,孩子结点
	//分裂前y有2t-1个关键字,分裂后前t-1个属于y,后t-1个属于z,中间第t个属于x
	node *z = new node(t-1, y->leaf);
	y->n = t - 1;
	//后t-1个关键字依次复制给z
	for(j = 1; j < t; j++)
		z->key[j] = y->key[j+t];
	//如果有孩子,孩子也要复制过去,原来有2t个子树,前t个属于y,后t个属于z
	if(y->leaf == false)
	{
		for(j = 1; j <= t; j++)
			z->child[j] = y->child[j+t];
	}
	//使z作为x的第i+1个孩子(y已经是x的第i个孩子)
	for(j = x->n+1; j > i; j--)
		x->child[j+1] = x->child[j];
	x->child[i+1] = z;
	//把y中第t个关键字插入到x的第i个位置
	for(j = x->n; j >= i; j--)
		x->key[j+1] = x->key[j];
	x->key[i] = y->key[t];
	//x的关键字+1
	x->n++;
	y->Disk_Write();
	z->Disk_Write();
	x->Disk_Write();
}
//将关键字k插入到以未满结点k为根结点的子树中,实际上是把k插入到叶结点,通过递归,直到遇到叶结点,再插入
void B_Tree::B_Tree_Insert_Nonfull(node *x, char k)
{
	int i = x->n;//从后向前遍历,再插入
	//实际上是把k插入到叶结点,else的操作只是往下降,遇到满结点进行分裂,最终都是要插入到叶结点中
	//若x是叶子结点,递归结束条件
	if(x->leaf)
	{
		//找到该插入的位置
		while(i >= 1 && k < x->key[i])
		{
			x->key[i+1] = x->key[i];
			i--;
		}
		//插入关键字k
		x->key[i+1] = k;
		x->n++;
		x->Disk_Write();
	}
	//若不是叶子结点
	else
	{
		//找到该插入的位置
		while(i >= 1 && k < x->key[i])
			i--;
		i++;
		//读取其孩子,将关键字插入到它的孩子中,分两种情况
		x->child[i]->Disk_Read();
		//孩子已满
		if(x->child[i]->n == 2 * t - 1)
		{
			//对孩子执行分裂操作,分裂后,孩子变为不满
			B_Tree_Split_Child(x, i, x->child[i]);
			//x->key[i]为刚从x的子结点升上来的关键字,如果k比它大,则k要插入到第i个结点后面的结点的孩子中去
			if(k > x->key[i])
				i++;
		}
		//孩子不满,直接对孩子进行插入操作
		B_Tree_Insert_Nonfull(x->child[i], k);
	}
}
//向T中插入关键字k
void B_Tree::B_Tree_Insert(char k)
{
	node *r = root, *s;
	//若根结点已满
	if(r->n == 2*t-1)
	{
		//申请一个新的结点,将新的结点作为根结点
		root = new node(0, false);
		root->child[1] = r;
		//将原根结点分裂为两个结点,分别作为s的第0个孩子和第1个孩子
		B_Tree_Split_Child(root, 1, r);
		//把关键字k插入到根结点中,此时根结点一定不满
		B_Tree_Insert_Nonfull(root, k);
	}
	//若根结点不满
	else
		//直接把关键字插入到根结点中
		B_Tree_Insert_Nonfull(r, k);
}
//删除T树中关键字为k的结点,由于是递归方法,当前处理的是x结点
void B_Tree::B_Tree_Delete(node *x, char k)
{
	int i, j;
	//找到x中第一个不小于k的关键字,即待处理的位置
	for(i = 1; i <= x->n; i++)
		if(x->key[i] >= k)
			break;
	//y是关键字k之前的结点,即小于k的最大孩子
	//z是关键字k之后的结点,即大于k的最小孩子
	node *y = x->child[i], *z = x->child[i+1], *d;
	//若关键字k在结点x中的第i个位置
	if(x->key[i] == k && i <= x->n)
	{
		//1)y是叶子结点,则直接从x中删除k
		if(x->leaf == true)
		{
			//关键字依次前移
			for(j = i; j < x->n; j++)
				x->key[j] = x->key[j+1];
			//关键字数-1
			x->n--;
			return;
		}
		//2)x是内结点
		//2-a:x中前于k的子结点y包含至少t个关键字
		if(y->n >= t)
		{
			//找出k在以y为根的子树中的前驱d
			d = y;
			while(d->leaf == false)
				d = d->child[d->n+1];
			//用d取代k
			x->key[i] = d->key[d->n];
			//递归地删除d
			B_Tree_Delete(y, d->key[d->n]);
		}
		//2-b:x是位于k之后的子结点z包含至少t个关键字
		else if(z->n >= t)
		{
			//找出k在以z为根的子树中的后继d
			d = z;
			while(d->leaf == false)
				d = d->child[1];
			//用d取代k
			x->key[i] = d->key[1];
			//递归地删除d
			B_Tree_Delete(z, d->key[1]);
		}
		//2-c:y和z都只有t-1个关键字,将k和z中所有关键字合并进y,使得x失去k和指向z的指针
		else
		{
			//将k关键字合并进y
			y->key[y->n+1] = k;
			//将z中所有关键字合并进y
			for(j = 1; j <= z->n; j++)
				y->key[y->n+j+1] = z->key[j];
			//如果有孩子,孩子也要合并
			if(y->leaf == false)
			{
				//使得x指向z的指针
				for(j = 1; j <= z->n+1; j++)
					y->child[y->n+j+1] = z->child[j];
			}
			//y包含2t-1个关键字
			y->n = y->n + 1 + z->n;
			//使得x失去k
			for(j = i; j < x->n; j++)
				x->key[j] = x->key[j+1];
			//使x失去指向z的指针
			for(j = i+1; j <= x->n; j++)
				x->child[j] = x->child[j+1];
			x->n--;
			//如果x是根结点,x
			if(x->n == 0 && root == x)
				root = y;
			//释放z
			delete z;
			//将k从y中递归删除
			B_Tree_Delete(y, k);
		}
	}
	//3)关键字不在结点x中,则必定包含k的正确的子树的根x->child[i]
	else
	{
		//x是叶子结点,找到根结点都没有找到k,则k不在树中
		if(x->leaf == true)
		{
			cout<<"error:not exist"<<endl;
			return;
		}
		//x是内结点
		//3-a:child[i]中只有t-1个关键字
		if(y->n == t-1)
		{
			//它的相邻兄弟x->child[i+1](用z表示)包含至少t个关键字
			if(i <= x->n && i <= x->n && z->n >= t)
			{
				//将x中的关键字下降至y
				y->n++;
				y->key[y->n] = x->key[i];
				//将z的某一关键字上升至x
				x->key[i] = z->key[1];
				for(j = 1; j < z->n; j++)
					z->key[j] = z->key[j+1];
				//将z适合的子女指针移到y
				if(y->leaf == false)
				{
					y->child[y->n+1] = z->child[1];
					for(j = 1; j <= z->n; j++)
						z->child[j] = z->child[j+1];
				}
				//z的关键字数-1
				z->n--;
			}
			//它的相邻兄弟x->child[i-1]包含至少t个关键字
			else if(i > 1 && x->child[i-1]->n >= t )
			{
				//将x中的关键字下降至y
				for(j = y->n; j >= 1; j--)
					y->key[j+1] = y->key[j];
				y->key[1] = x->key[i-1];
				y->n++;
				//将y的相邻兄弟x->child[i-1]的某一关键字上升至x
				x->key[i-1] = x->child[i-1]->key[x->child[i-1]->n];
				//将该兄弟适合的子女指针移到y
				if(y->leaf == false)
				{
					for(j = y->n; j >= 1; j--)
						y->child[j+1] = y->child[j];
					y->child[1] = x->child[i-1]->child[x->child[i-1]->n+1];
				}
				//x->child[i-1]的关键字数-1
				x->child[i-1]->n--;
			}
			//y和其所有相邻兄弟都只有t-1个关键字,则与其中一个兄弟合并
			else
			{
				//与后面一个结点(用z表示)合并
				if(i <= x->n)
				{
					//将x->key[i]并入y中
					y->key[y->n+1] = x->key[i];
					//将z中所有关键字并入y中
					for(j = 1; j <= z->n; j++)
						y->key[j+y->n+1] = z->key[j];
					//如果有孩子,所有孩子也要并入
					if(y->leaf == false)
					{
						for(j = 1; j <= z->n+1; j++)
							y->child[j+y->n+1] = z->child[j]; 
					}
					//修改y的关键字数
					y->n = y->n + 1 + z->n;
					//将x->key[i]从x中移出
					for(j = i; j < x->n; j++)
						x->key[j] = x->key[j+1];
					//把指向z的指针从x->child中移出
					for(j = i+1; j <= x->n; j++)
						x->child[j] = x->child[j+1];
					//x的关键字数-1
					x->n--;
					//若根结点被删除,更新根结点
					if(x->n==0 && root == x)
						root = y;
				}
				//与前面一个结点合并
				else
				{
					//令z=x->child[i-1],y=x->child[i],把z并入y中
					z = y;i--;
					y = x->child[i];
					//将x->key[i]并入y中
					y->key[y->n+1] = x->key[i];
					//将z中所有关键字并入y中
					for(j = 1; j <= z->n; j++)
						y->key[j+y->n+1] = z->key[j];
					//如果有孩子,所有孩子也要并入
					if(y->leaf == false)
					{
						for(j = 1; j <= z->n+1; j++)
							y->child[j+y->n+1] = z->child[j]; 
					}
					//修改y的关键字数
					y->n = y->n + 1 + z->n;
					//将x->key[i]从x中移出
					for(j = i; j < x->n; j++)
						x->key[j] = x->key[j+1];
					//把指向z的指针从x->child中移出
					for(j = i+1; j <= x->n; j++)
						x->child[j] = x->child[j+1];
					//x的关键字数-1
					x->n--;
					//若根结点被删除,更新根结点
					if(x->n==0 && root == x)
						root = y;
				}
			}
		}
		//递归执行删除操作
		B_Tree_Delete(y, k);
	}
}
//按关键字从小到大输出结点
void B_Tree::Print(node *n)
{
	int i;
	for(i = 1; i <= n->n; i++)
	{
		if(n->leaf == false)
			Print(n->child[i]);
		cout<<n->key[i]<<' ';
	}
	if(n->leaf == false)
		Print(n->child[n->n+1]);
}

main.cpp


#include <iostream>
using namespace std;
#include "B_Tree.h"
int main()
{
	//测试数据
	char ch[] = {'F','S','Q','K','C','L','H','T','V','W','M','R','N','P','A','B','X','Y','D','Z','E'};
	//生成一棵B树
	B_Tree *T = new B_Tree;
	T->B_Tree_Create();
	//依次插入关键字
	cout<<"插入测试"<<endl;
	int i;
	for(i = 0; i < 21; i++)
	{
		T->B_Tree_Insert(ch[i]);
		T->Print(T->root);
		cout<<endl;
	}
	//输出这棵树
	T->Print(T->root);
	cout<<endl;
	//B树删除操作测试
	cout<<"查找与删除测试"<<endl;
	char c;
	for(i = 0; i < 100; i++)
	{
		cin>>c;
		T->B_Tree_Delete(T->root, c);
		T->Print(T->root);
		cout<<endl;
	}
	return 0;
}

转自:http://blog.csdn.net/mishifangxiangdefeng/article/details/7798672
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值