B-树自在人心|B树的原理与实现

10 篇文章 1 订阅
8 篇文章 0 订阅

数据结构|AVL树(平衡树)的理解与代码实现与讲解(尽可能写清楚)

B树原理

B树也是一种平衡树,它有以下几个明显特征:
高度一致:所有叶子结点在同一个水平!
结点阶数m
结点阶数m即为该结点指针数目,最大子树数目
m阶数也称m叉树
在这里插入图片描述
如3阶B-树中,阶树m值为3;关键字最大值n为2

结点子树(非空指针数目)

每个结点至多有m颗子树

根结点子树非空子树:  2~m

终端结点子树非空子树:0  但是空指针数为m个

非根结点和非终端结点非空子树:[m/2]~m个

若根结点不是叶子结点,则至少有两颗子树
(这句话我的理解是,如果一颗树只有一个结点,即根结点也是叶子结点,那么该结点无非空子树,否则其他除叶子结点之外的所有结点,都一定要有两颗以上非空子树

结点关键字树n
结点内存放的元素称为关键字

结点的关键字个数范围从0<=n<m-1个

根节点关键字数:1<=n<=m-1

终端结点,至少有[m/2]-1<=n<=m-1个关键字;
如3阶B-树终端结点关键字为:[3/2]-1=1
5阶B-树终端结点关键字个数为:[5/2]-1=2

非根结点和非终端结点关键字数目n为:[m/2]-1<=n<=m-1


[]为向上取整

归纳
从根结点开始:根结点关键字数至少为1,最多为m-1;非空子树为2~m
非根结点和非终端结点的中间结点:关键字数为至少[m/2]-1至多m-1;非空子树至少[m/2],至多m;
终端结点:关键字数至少[m/2]-1至多m-1;非空子树为0,空子树为m
一颗B-树总共有n个关键字,则空孩子树总共有n+1个。
(每个结点子树一定有m颗,分为空和非空子树)

高度
包括终端结点的子树(NULL,型):所有叶子结点都必须处于同一高度

在这里插入图片描述

上面左图中,虽然子树不存在,其高度也不存在,但是他们的叶子结点是在同一水平线上(虽然不存在)

但在右图中,5的叶子结点从9出发一直查找到了最下面的第四层null,而最近的叶子结点,从3出发,到第3层停止了。
所以右图不满足B-树。


插入B-树
插入B树的时候要满足**[m/2]**的原则:
当一个结点的关键字数目达到了m个时,要从[m/2]开始拆开这个结点
B树自在人心,看不懂,我当场把这个树吃掉!

B树实现

C语言-B树(B-树)的完整实现
代码是参照这个大佬的,它的删除部分有不足,而且未考虑重复项。

首先,得理清思路,B树不像之前的数据结构那么直接
1.当我插入一个关键字时,首先要查找这个关键字应该插在哪个位置!
2.如果是插入的第一个元素时,和后续的元素时 会有不同的处理方式
因为第一个元素,就没必要去进行查找过程,直接插入第一个位置就行

查找过程

/*这里查找到的i都是是有实际元素的关键字位置,当i不是第一个关键字时,
i+1都是和插入的关键字k有关,要不是它后面插入的位置,要不是树的这个结点,
往下面继续查找的孩子指针*/
Status SerchBTNode(BTNode *p, KeyType x)
{
	int i;
	for (i = 0; i < p->KeyNum&&p->key[i + 1] <= x; i++);//所有插入的位置 都是i后面一个
	return i;
}

Result SerchBTree(BTree tree, KeyType k)
{
	BTNode *q = tree, *p = NULL;
	int found_tag = 0, i = 0;
	Result r;
	while (q != NULL&&found_tag == 0)//found_tag  是查找成功的标志
	{									//当空树时没有必须查找
		i = SerchBTNode(q, k);//查找k适合插入的位置
		if (i > 0 && q->key[i] == k)
		{
			found_tag = 1;
		}
		else
		{
			p = q;
			q = q->child[i];//这一步很关键,如果在第一个结点没有查到,而根据其大小,在第一个结点找到其孩子指针,然后这里转向孩子 指针 继续查找
		}
	}
	if (found_tag == 1){
		r.i = i;
		r.p = q;
		r.tag = 1;
	}
	else
	{
		r.i = i;
		r.p = p;
		r.tag = 0;
	}
	return r;
}

上面四个是插入过程的函数,还涉及到两个查找原结点中是否有关键字k的函数 并返回相应的结构体,结构体保存了,被插入的结点,插入的位置(找到的是i,但插入的是i+1)

这一步是往其孩子结点找到适合的位置(在这之前找到了,其适合的孩子位置)

			p = q;
			q = q->child[i];

插入过程

插入过程涉及四个环节想想就
第一:当树不存在,此时插入的关键字将成为根结点元素
所以要新生成树的结点
当是树的第一个结点时,其孩子 父亲结点都为NULL
而这个函数也会出现在后面第四种环节中
当要拆开一个结点时,是一变二,也可能是一变三
何为一变三,即要分离的那个关键字,往上走时,上面就没有结点,这个时候要新生成一个结点,其孩子指针是分裂的两个结点

/*生成新的结点*/
void NewRoot(BTNode *&tree, KeyType k, BTNode *p, BTNode *q)
{
	tree = new Node;
	tree->key[1] = k;
	tree->KeyNum = 1;
	tree->child[0] = p;
	tree->child[1] = q;
	/*非根结点的话 pq可能存在合法的结点 就要修改其父结点*/
	if (p != NULL)
	{
		p->parent = tree;
	}
	if (q != NULL)
	{
		q->parent = tree;
	}
	tree->parent = NULL;
}

第二个环节就是,当树不是一个空树时,(可能要面临拆开的环节)
上面说了空树新生一个结点
那么树不为空树时,这里的一个过程就是在查找过程所得的结点位置p i(i+1),插入关键字k,当然也要新插入一个空孩子

void InSertBTNode(BTNode *&p, int i, KeyType k, BTNode *q)
{
	/*在第i个位置 插入*/
	/*涉及到两种情况 1.插在末尾;2.插在中间*/
	int j;
	for (j = p->KeyNum; j > i; j--)/*往后移*/
	{
		p->key[j + 1] = p->key[j];
		p->child[j + 1] = p->child[j];
	}
	p->key[i + 1] = k;/*前面查找过程 所得的i是插入的前一个位置坐标*/
	p->child[i + 1] = q;
	if (q != NULL)
	{
		q->parent = p;
	}
	p->KeyNum++;//关键字数目+1
}

当插入成功之后,就要判断是否需要拆分(所以在定义结构体时多保留一位)

//将结点p分裂成两个结点,前一半保留,后一半移入结点q
void SplitBTNode(BTNode *&p, BTNode *&q)
{
	q = new Node;						//给结点q分配空间
	int s = NUM;
	q->child[0] = p->child[s];				//后一半移入结点q  并置空
	p->child[s] = NULL;
	for (int i = s + 1; i <= m; i++)
	{
		q->child[i - s] = p->child[i];
		q->key[i - s] = p->key[i];
		p->child[i] = NULL;
		p->key[i] = NULL;
	}

	/*修改结构体内容*/
	q->KeyNum = p->KeyNum - s;
	q->parent = p->parent;
	/*修改 拿过来的子树双亲*/
	for (int j = 0; j <= q->KeyNum; j++)
	{
		if (q->child[j] != NULL)
		{
			q->child[j]->parent = q;
		}
	}
	p->KeyNum = s - 1;
}

/*完成插入的函数

/*在一颗树的结点p的第i个位置插入关键字k*/
void InsertBTree(BTree &tree, int i, KeyType k, BTNode *p){
	/*插入一个新的关键字的话,同时也会生成一个新的子树  尽管可能为空*/
	BTNode *q;
	int finish_tag, newroot_tag;/*插入成功的标志 和新的结点生成的标志*/		/*如果超过最大关键字数  而往上走没有合适的结点放时就要生成新的结点*/
	KeyType x;

	if (p == NULL)
	{
		/*插入的时候 树空 结点也空*//*这时候就要新生成一颗树了*/
		NewRoot(tree, k, NULL, NULL);
	}
	else
	{
		x = k;
		q = NULL;
		finish_tag = 0, newroot_tag = 0;
		while (finish_tag == 0 && newroot_tag == 0)
		{
			InSertBTNode(p, i, x, q);/*插入结点p中  关键字插入也要生成一个子树(即为空)*/
			if (p->KeyNum <= MAX)/*判断关键字数目是否合法*/
			{
				finish_tag = 1;/*合法 则修改循环标志  插入成功*/
			}
			else/*不合法 即关键字数多了 则拆分结点*/
			{
				int s = NUM;
				SplitBTNode(p, q);/*以s为分界线  将p拆开  */
				/*处理分割点上的元素    两种情况*/
				x = p->key[s];

				if (p->parent)/*如果双亲存在 寻找适合插入的位置*/
				{
					p = p->parent;
					i = SerchBTNode(p, x);
					//i = SerchBTNode(p->parent, x);/*回到while循环 又开始一场新的插入位置  此时  分裂结点 将变为新的插入结点*/
				}
				else{/*向上走 没有合适的可以插入 那么新生成一个结点咯*/
					newroot_tag = 1;
				}
			}
		}
		if (newroot_tag == 1){/*要生成新的结点 说明 原来拆散的是根结点   这里将新生成一个新的根结点*/
			NewRoot(tree, x, p, q);
		}
	}
}

打印输出

直接输出

BTree PrintTree(BTree &tree)
{
	if (tree != NULL){
		cout << "[";
		for (int i = 1; i <= tree->KeyNum; i++)
		{
			cout << " " << tree->key[i];
		}
		cout << " ]" ;
		for (int j = 0; j < tree->KeyNum+1; j++)
		{
			if (tree->child[j] != NULL){
				PrintTree(tree->child[j]);
			}
		}
	}
	return tree;
}

有顺序的打印输出(学到了)
思路:从上之下,从左往右
而这个不是简单的二叉树,两个遍历就行
要用到队列:
先将第一行第一个结点的左子树放入队列的第一个有效结点
然后将第一行结点的关键字依序输出,且将后面的孩子树,依次存入队列

Enqueue(L, tree->child[0]);/*现将最左边的孩子结点入队列*/			//每次入队列 都是排在最后,所以达到了一行一行的处理
		for (i = 1; i <= tree->KeyNum; i++){
			cout << tree->key[i]<<" ";
			Enqueue(L, tree->child[i]);/*后面的孩子接着上*/
		}

这样在输入第一行的关键字同时,达到了孩子树入队的效果,且后入的再后面

sum += tree->KeyNum + 1;//用来记录存放的孩子数目

后面根据标识来判断是是否需要换行

if (newLine == 0){
			newLine = sum - 1;/*即将处理一个孩子 结点 所以记录下其他孩子数*/
			sum = 0;/*归0,后续处理*/
			cout << endl;
		}
		else{
			newLine--;
		}

删除过程

删除过程原理

在这里插入图片描述

删除部分代码实现

1.先找到被删的关键字所在位置(P1,i1)
2.判断是终端结点(直接删)还是非终端结点(寻找合适的叶子结点关键字取代它,然后变成删除叶子结点)
3.并把该结点(P1)其他关键字做排序移位置处理
4.根据被删结点的关键字数量进行判断是否需要做调整
5.调整看我那部分代码就行 左旋右旋 左合并右合并。

查找被删除的关键字,返回其所在结点,和位置

//先查找要删除的关键字,在树中所在的结点关键字数组中的位置
//返回值有三种,结点不存在时返回0
//存在时返回1
//该结点中没有,但是可以从该结点往下走,返回下一个孩子指针的位置
Status FindKeyTree(BTNode *p, int &i,KeyType k)
{
	if (k<p->key[1]){
		//cout << "该结点中不存在该关键字"<< endl;
		i = 0;//关键字数组0不存储元素
		return 0;
	}
	else{
		i = p->KeyNum;
		while (k < p->key[i] && i>1){
			i--;
		}
		if (k == p->key[i]){
			return 1;
		}
		else
			return 0;
	}
}

删除关键字

/*直接删除——关键字数目大于最小关键字数,可以直接删去*/
/*直接删除结点中位置i处的关键字——适用于,结点中关键字大于最小关键字数的终端结点*/
/*删除值直接将关键字往前移,在右边空了出来(后面赋值处理)*/
void ReMove(BTNode *p, int i){
	int j;
	for (j = i + 1; j <= p->KeyNum; j++){
		p->key[j - 1] = p->key[j];
		p->child[j - 1] = p->child[j];
	}
	p->key[p->KeyNum] = '\0';
	p->KeyNum--;
}

相对于根结点,向左右子树借关键字

//是位于相对根结点向左子树借最大关键字,和位于相对根结点向柚子树借最小关键字——这里只是复制了一下
void SubstitutionLeft(BTNode *p, int i)
{
	//p是寻找到相对根结点,i是其中需要替换的关键字的位置,思路寻找左子树最大关键字
	BTNode *q;
	for (q = p->child[i - 1]; q->child[q->KeyNum] != NULL; q = q->child[q->KeyNum]);
	p->key[i] = q->key[q->KeyNum - 1];
}
void SubstitutionRight(BTNode *p, int i)//向右寻找
{
	BTNode *q;
	for (q = p->child[i]; q->child[0] != NULL; q = q->child[0]);
	p->key[i] = q->key[1];
}

删除之后的左旋转,右旋转

这里一定要注意p和i是指向被删关键字的双亲位置,和双亲指向被删关键字的指针。

这部分一定要自己画个图就会有助于理解其中含义(上面有),重点不仅是如何旋转,还有i p的位置。
左旋,右旋

/*这里的左旋 右旋是在删除了关键字之后,对树做调整而写*/
//所以p是被删关键字的结点,i是指向关键字的的孩子指针位置
void MoveRight(BTNode *p, int i)
{
	int j;
	BTNode *q=p->child[i];//被删除的结点
	BTNode *aq = p->child[i - 1];//被借的兄弟结点  当用到这个兄弟结点时,已经筛选了,它的关键字数大于最小量才用
								//而且选取左子树最大一个取代
	/*之前删除的时候,已经将被删结点,做了往左走的处理*/
	/*父结点i处的元素应该赋予在被删结点的最左边,所以要做右走处理*/
	for (j = q->KeyNum; j >= 1; j--){
		q->key[j + 1] = q->key[j];
		q->child[j + 1] = q->child[j];
	}
	q->child[1] = q->child[0];
	q->key[1] = p->key[i];
	q->KeyNum++;

	/*左子树到相对根结点*/
	p->key[i] = aq->key[aq->KeyNum];
	p->child[i]->child[0] = aq->child[aq->KeyNum];//在向左兄弟借孩子时,把它的最大孩子指针,带到右兄弟的最小孩子指针
	aq->KeyNum--;
}
void MoveLeft(BTNode *p, int i)/*左旋*/
{
	int j;
	BTNode *q, *aq;
	q = p->child[i+1];//右兄弟借孩子
	aq = p->child[i];//左兄弟被删的
	/*左旋转,相对根的关键字放在,左子树的最大关键字处*/
	//而删除操作时,最大位置处是做空的
	aq->key[aq->KeyNum + 1] = p->key[i + 1];
	aq->KeyNum++;
	aq->child[aq->KeyNum] = p->child[i + 1]->child[0];/*画图得知,向右兄弟借孩子的同时,把它的0号孩子指针赋予左兄弟的最大孩子指针*/
	/*把右兄弟的孩子(选择最小的)替换掉父结点关键字*/
	p->key[i+1]=q->key[1];
	q->child[0]=q->child[1];
	//向前移
	for (j = 2; j <= q->KeyNum; j++){
		q->key[j-1]=q->key[j];
		q->child[j - 1] = q->child[j];
	}
	q->KeyNum--;
}


左合并 右合并

/*,被删结点的左兄弟不能合并与右兄弟合并*/
void CombineRight(BTNode *p, int i)
{
	int j,count=0;
	BTNode *q = p->child[i];//被删结点  /*左兄弟是被删的,且最右边已经空了*/
	BTNode *aq=p->child[i+1];//右兄弟
	
	q->KeyNum++;/*步骤1:先将双亲结点第i+1个元素拿到左兄弟中*/
	q->key[q->KeyNum] = p->key[i+1];

	for (j = i+2; j <= p->KeyNum; j++)/*步骤2:把根结点、孩子指针往左移一个*/
	{
		p->key[j - 1] = p->key[j];
		p->child[j - 1] = p->child[j];
	}
	p->child[p->KeyNum] = NULL;
	p->KeyNum--;
	/*步骤3:将右兄弟的关键字与左兄弟合并*/
	q->child[q->KeyNum]=aq->child[0];
	for (j = 1; j <= aq->KeyNum; j++)
	{
		q->key[q->KeyNum + j] = aq->key[j];
		q->child[q->KeyNum+j]=aq->child[j]; 
		count++;
	}
	q->KeyNum += count;
	free(aq);
}

void CombineLeft(BTNode *p, int i){
	/*也是向左合并*/
	int j, count = 0;
	BTNode *q = p->child[i];/*右兄弟是被删的,且最其右边已经空了*/
	BTNode *aq = p->child[i - 1];//左兄弟

	/*步骤1:先将双亲结点一个元素拿到右兄弟中的第一个元素*/
	for (j = q->KeyNum; j >= 1; j--){/*对右兄弟进行右移处理*/
		q->key[j+1]=q->key[j];
		q->child[j+1]=q->child[j];
	}
	q->child[1]=q->child[0];
	q->key[1]=p->key[i];
	p->key[i] = '\0';
	q->KeyNum++;
	/*步骤2:将被删结点往左兄弟移*/
	for (j = 1; j <= q->KeyNum; j++){
		aq->key[aq->KeyNum + j] = q->key[j];
		aq->child[aq->KeyNum + j] = q->child[j];
		q->key[j] = '\0';
		q->child[j] = NULL;
		count++;
	}
	aq->KeyNum += count;
	q->KeyNum -= count;
	/*步骤3: 将根结点左移*/
	for (j = i + 1; j <= p->KeyNum; j++){
		p->key[j - 1] = p->key[j];
		p->child[j - 1] = p->child[j];
	}
	p->KeyNum--;
	free(q);
}

完整代码

#include<iostream>
using namespace std;
#define m 3
#define MAXM 15
const int MAX = m - 1;//关键字结点数目
const int NUM = (m + 1) / 2;//分割点  [m/2]向上取整
const int MIN = NUM - 1;

typedef int KeyType;
typedef int Status;

typedef struct Node{
	int KeyNum;
	KeyType key[MAXM];//原本想在刚刚好的数组,但是如果关键字数目大于m-1时,这是关键字x就必须先将结点拆开 才能放,目前来看麻烦//所以选择将数组扩大一个
	struct Node *parent;
	struct Node *child[MAXM];
}BTNode, *BTree;

//插入之前,在树中查找是否存在关键字x;
//存在即插入失败,//不存在即可以插入,记下插入的结点 结点中关键字的位置 和记录查找 成功与否的标志
typedef struct Result{
	Node *p;
	int i;
	int tag;
}Result;


/*为了有型输出B树,需要构造一个队列 其内容是BTree*/
typedef struct LNode{
	BTree data;
	LNode *next;
}*LNodeList;

/*输出所用的一个链表*//*现在搞生成 这个后面说  不就是多叉树嘛*/

void InsertBTree(BTree &tree, int i, KeyType k, BTNode *p);
void NewRoot(BTNode *&tree, KeyType k, BTNode *p, BTNode *q);
void InSertBTNode(BTNode *&p, int i, KeyType k, BTNode *q);
void SplitBTNode(BTNode *&p, BTNode *&q);

/*上面四个是插入过程的函数,还涉及到两个查找原结点中是否有关键字k的函数 并返回相应的结构体*/
Result SerchBTree(BTree tree, KeyType k);/*查找树中的  k字*/
Status SerchBTNode(BTNode *p, KeyType x);/*在树的具体结点中查找k字*/

/*判断链表是否为空*/
Status IfEmpty(LNodeList L);

Status SerchBTNode(BTNode *p, KeyType x)
{
	int i;
	for (i = 0; i < p->KeyNum&&p->key[i + 1] <= x; i++);//所有插入的位置 都是i后面一个
	return i;
}

Result SerchBTree(BTree tree, KeyType k)
{
	BTNode *q = tree, *p = NULL;
	int found_tag = 0, i = 0;
	Result r;
	while (q != NULL&&found_tag == 0)//found_tag  是查找成功的标志
	{
		i = SerchBTNode(q, k);
		if (i > 0 && q->key[i] == k)
		{
			found_tag = 1;
		}
		else
		{
			p = q;
			q = q->child[i];//这一步很关键,如果在第一个结点没有查到,而根据其大小,在第一个结点找到其孩子指针,然后这里转向孩子 指针 继续查找
		}
	}
	if (found_tag == 1){
		r.i = i;
		r.p = q;
		r.tag = 1;
	}
	else
	{
		r.i = i;
		r.p = p;
		r.tag = 0;
	}
	return r;
}


/*在一颗树的结点p的第i个位置插入关键字k*/
void InsertBTree(BTree &tree, int i, KeyType k, BTNode *p){
	/*插入一个新的关键字的话,同时也会生成一个新的子树  尽管可能为空*/
	BTNode *q;
	int finish_tag, newroot_tag;/*插入成功的标志 和新的结点生成的标志*/		/*如果超过最大关键字数  而往上走没有合适的结点放时就要生成新的结点*/
	KeyType x;

	if (p == NULL)
	{
		/*插入的时候 树空 结点也空*//*这时候就要新生成一颗树了*/
		NewRoot(tree, k, NULL, NULL);
	}
	else
	{
		x = k;
		q = NULL;
		finish_tag = 0, newroot_tag = 0;
		while (finish_tag == 0 && newroot_tag == 0)
		{
			InSertBTNode(p, i, x, q);/*插入结点p中  关键字插入也要生成一个子树(即为空)*/
			if (p->KeyNum <= MAX)/*判断关键字数目是否合法*/
			{
				finish_tag = 1;/*合法 则修改循环标志  插入成功*/
			}
			else/*不合法 即关键字数多了 则拆分结点*/
			{
				int s = NUM;
				SplitBTNode(p, q);/*以s为分界线  将p拆开  */
				/*处理分割点上的元素    两种情况*/
				x = p->key[s];
				p->key[s] = '\0';
				if (p->parent)/*如果双亲存在 寻找适合插入的位置*/
				{
					p = p->parent;
					i = SerchBTNode(p, x);/*回到while循环 又开始一场新的插入位置  此时  分裂结点 将变为新的插入结点*/
				}
				else{/*向上走 没有合适的可以插入 那么新生成一个结点咯*/
					newroot_tag = 1;
				}
			}
		}
		if (newroot_tag == 1){/*要生成新的结点 说明 原来拆散的是根结点   这里将新生成一个新的根结点*/
			NewRoot(tree, x, p, q);
		}
	}
}

/*生成新的结点*/
void NewRoot(BTNode *&tree, KeyType k, BTNode *p, BTNode *q)
{
	tree = new Node;
	tree->key[1] = k;
	tree->KeyNum = 1;
	tree->child[0] = p;
	tree->child[1] = q;
	/*非根结点的话 pq可能存在合法的结点 就要修改其父结点*/
	if (p != NULL)
	{
		p->parent = tree;
	}
	if (q != NULL)
	{
		q->parent = tree;
	}
	tree->parent = NULL;
}

void InSertBTNode(BTNode *&p, int i, KeyType k, BTNode *q)
{
	/*在第i个位置 插入*/
	/*涉及到两种情况 1.插在末尾;2.插在中间*/
	int j;
	for (j = p->KeyNum; j > i; j--)/*往后移*/
	{
		p->key[j + 1] = p->key[j];
		p->child[j + 1] = p->child[j];
	}
	p->key[i + 1] = k;/*前面查找过程 所得的i是插入的前一个位置坐标*/
	p->child[i + 1] = q;
	if (q != NULL)
	{
		q->parent = p;
	}
	p->KeyNum++;//关键字数目+1
}
//将结点p分裂成两个结点,前一半保留,后一半移入结点q
void SplitBTNode(BTNode *&p, BTNode *&q)
{
	q = new Node;						//给结点q分配空间
	int s = NUM;
	q->child[0] = p->child[s];				//后一半移入结点q  并置空
	p->child[s] = NULL;
	for (int i = s + 1; i <= m; i++)
	{
		q->child[i - s] = p->child[i];
		q->key[i - s] = p->key[i];
		p->child[i] = NULL;
		p->key[i] = NULL;
	}

	/*修改结构体内容*/
	q->KeyNum = p->KeyNum - s;
	q->parent = p->parent;
	/*修改 拿过来的子树双亲*/
	for (int j = 0; j <= q->KeyNum; j++)
	{
		if (q->child[j] != NULL)
		{
			q->child[j]->parent = q;
		}
	}
	p->KeyNum = s - 1;
}

/*输出所有的关键字*//*但是无法按照顺序来*/
BTree PrintTree(BTree &tree)
{
	if (tree != NULL){
		cout << "[";
		for (int i = 1; i <= tree->KeyNum; i++)
		{
			cout << " " << tree->key[i];
		}
		cout << " ]" ;
		for (int j = 0; j < tree->KeyNum+1; j++)
		{
			
			//tree = tree->child[j];
			if (tree->child[j] != NULL){
				/*if (j == 0){
					cout << endl;
				}*/
				PrintTree(tree->child[j]);
			}
			/*cout <<" ";
			if (j == tree->KeyNum){
				cout << endl;
			}*/
		}
	}
	return tree;
}
/*尝试有形输出平衡树*/
//初始化一个队列
Status InitLinkList(LNodeList &L)
{
	L = new LNode;
	if (L == NULL){
		return NULL;
	}
	L->next = NULL;
	return 0;
}
/*队列构造成功之后,就要尝试将以B树 结点为内容的元素入队列了*/
Status Enqueue(LNode *L, BTNode *q){
	if (L == NULL){
		InitLinkList(L);
	}
	/*先进先出*/  /*所以找到最后一个结点,并在最后一个结点后面 新生成一个结点接上,并把树结点存进去*/
	while (L->next != NULL){
		L = L->next;
	}
	LNode *p;/*分配一个新的结点*/
	p = new LNode;
	if (p != NULL){
		p->data = q;
		p->next = NULL;
	}
	L->next = p;/*如果是第一个 也是存在 L->next*/
	return 1;
}
/*出队列*//*并以结点q返回*/
Status Dequeue(LNode *L,BTNode *&q){
	if (L == NULL || L->next == NULL)
	{
		return 0;
	}
	/*因为要从头开始释放,且每次只释放一个*/
	LNode *p;
	
	//p = new LNode;
	p=L->next;			//设置一个中间结点,寻找到的下一个结点赋予它,并改变前后指针
	L->next = p->next;
	q = p->data;
	free(p);
	return 0;
}

/*实现用队列有序输出B树*/
/*主要难在 一行一行的输出*/
Status PrintBTree(BTree tree,LNodeList L,int newLine,int sum){
	int i;
	BTree p;
	if (tree != NULL)
	{
		cout << " [ ";
		Enqueue(L, tree->child[0]);/*现将最左边的孩子结点入队列*/			//每次入队列 都是排在最后,所以达到了一行一行的处理
		for (i = 1; i <= tree->KeyNum; i++){
			cout << tree->key[i]<<" ";
			Enqueue(L, tree->child[i]);/*后面的孩子接着上*/
		}
		sum += tree->KeyNum + 1;/*记录存入了多少个孩子*/
		cout <<"]";

		/*用newLine来判断  是否需要孩子结点处理完毕*/
		if (newLine == 0){
			newLine = sum - 1;/*即将处理一个孩子 结点 所以记录下其他孩子数*/
			sum = 0;/*归0,后续处理*/
			cout << endl;
		}
		else{
			newLine--;
		}
	}
	if (IfEmpty(L) == 1){

		Dequeue(L,p);
		PrintBTree(p,L,newLine,sum);
	}
	return 0;
}
Status IfEmpty(LNodeList L){
	if (L == NULL)                                     //队列不存在 
		return 0;
	if (L->next == NULL)                               //队列为空 
		return 0;
	return 1;                                   //队列非空 
}
/***************************//*在树中删除关键字时的操作*//***************************/
//先查找要删除的关键字,在树中所在的结点关键字数组中的位置
//返回值有三种,结点不存在时返回0
//存在时返回1
//该结点中没有,但是可以从该结点往下走,返回下一个孩子指针的位置
Status FindKeyTree(BTNode *p, int &i,KeyType k)
{
	if (k<p->key[1]){
		//cout << "该结点中不存在该关键字"<< endl;
		i = 0;//关键字数组0不存储元素
		return 0;
	}
	else{
		i = p->KeyNum;
		while (k < p->key[i] && i>1){
			i--;
		}
		if (k == p->key[i]){
			return 1;
		}
		else
			return 0;
	}
}
/****************************//*具体删除操作*//************************/

/*直接删除——关键字数目大于最小关键字数,可以直接删去*/
/*直接删除结点中位置i处的关键字——适用于,结点中关键字大于最小关键字数的终端结点*/
/*删除值直接将关键字往前移,在右边空了出来(后面赋值处理)*/
void ReMove(BTNode *p, int i){
	int j;
	for (j = i + 1; j <= p->KeyNum; j++){
		p->key[j - 1] = p->key[j];
		p->child[j - 1] = p->child[j];
	}
	p->key[p->KeyNum] = '\0';
	p->KeyNum--;
}
/************//*关键字数目等于最小关键字数目,不能直接删,左右兄弟有大于最小关键数目,向他们借孩子*///**************

//是位于相对根结点向左子树借最大关键字,和位于相对根结点向柚子树借最小关键字——这里只是复制了一下
void SubstitutionLeft(BTNode *p, int i)
{
	//p是寻找到相对根结点,i是其中需要替换的关键字的位置,思路寻找左子树最大关键字
	BTNode *q;
	for (q = p->child[i - 1]; q->child[q->KeyNum] != NULL; q = q->child[q->KeyNum]);
	p->key[i] = q->key[q->KeyNum - 1];
}
void SubstitutionRight(BTNode *p, int i)//向右寻找
{
	BTNode *q;
	for (q = p->child[i]; q->child[0] != NULL; q = q->child[0]);
	p->key[i] = q->key[1];
}
/*这里的左旋 右旋是在删除了关键字之后,对树做调整而写*/
//所以p是被删关键字的结点,i是指向关键字的的孩子指针位置
void MoveRight(BTNode *p, int i)
{
	int j;
	BTNode *q=p->child[i];//被删除的结点
	BTNode *aq = p->child[i - 1];//被借的兄弟结点  当用到这个兄弟结点时,已经筛选了,它的关键字数大于最小量才用
								//而且选取左子树最大一个取代
	/*之前删除的时候,已经将被删结点,做了往左走的处理*/
	/*父结点i处的元素应该赋予在被删结点的最左边,所以要做右走处理*/
	for (j = q->KeyNum; j >= 1; j--){
		q->key[j + 1] = q->key[j];
		q->child[j + 1] = q->child[j];
	}
	q->child[1] = q->child[0];
	q->key[1] = p->key[i];
	q->KeyNum++;

	/*左子树到相对根结点*/
	p->key[i] = aq->key[aq->KeyNum];
	p->child[i]->child[0] = aq->child[aq->KeyNum];//在向左兄弟借孩子时,把它的最大孩子指针,带到右兄弟的最小孩子指针
	aq->KeyNum--;
}
void MoveLeft(BTNode *p, int i)/*左旋*/
{
	int j;
	BTNode *q, *aq;
	q = p->child[i+1];//右兄弟借孩子
	aq = p->child[i];//左兄弟被删的
	/*左旋转,相对根的关键字放在,左子树的最大关键字处*/
	//而删除操作时,最大位置处是做空的
	aq->key[aq->KeyNum + 1] = p->key[i + 1];
	aq->KeyNum++;
	aq->child[aq->KeyNum] = p->child[i + 1]->child[0];/*画图得知,向右兄弟借孩子的同时,把它的0号孩子指针赋予左兄弟的最大孩子指针*/
	/*把右兄弟的孩子(选择最小的)替换掉父结点关键字*/
	p->key[i+1]=q->key[1];
	q->child[0]=q->child[1];
	//向前移
	for (j = 2; j <= q->KeyNum; j++){
		q->key[j-1]=q->key[j];
		q->child[j - 1] = q->child[j];
	}
	q->KeyNum--;
}

/*,被删结点的左兄弟不能合并与右兄弟合并*/
void CombineRight(BTNode *p, int i)
{
	int j,count=0;
	BTNode *q = p->child[i];//被删结点  /*左兄弟是被删的,且最右边已经空了*/
	BTNode *aq=p->child[i+1];//右兄弟
	
	q->KeyNum++;/*步骤1:先将双亲结点第i+1个元素拿到左兄弟中*/
	q->key[q->KeyNum] = p->key[i+1];

	for (j = i+2; j <= p->KeyNum; j++)/*步骤2:把根结点、孩子指针往左移一个*/
	{
		p->key[j - 1] = p->key[j];
		p->child[j - 1] = p->child[j];
	}
	p->child[p->KeyNum] = NULL;
	p->KeyNum--;
	/*步骤3:将右兄弟的关键字与左兄弟合并*/
	q->child[q->KeyNum]=aq->child[0];
	for (j = 1; j <= aq->KeyNum; j++)
	{
		q->key[q->KeyNum + j] = aq->key[j];
		q->child[q->KeyNum+j]=aq->child[j]; 
		count++;
	}
	q->KeyNum += count;
	free(aq);
}

void CombineLeft(BTNode *p, int i){
	/*也是向左合并*/
	int j, count = 0;
	BTNode *q = p->child[i];/*右兄弟是被删的,且最其右边已经空了*/
	BTNode *aq = p->child[i - 1];//左兄弟

	/*步骤1:先将双亲结点一个元素拿到右兄弟中的第一个元素*/
	for (j = q->KeyNum; j >= 1; j--){/*对右兄弟进行右移处理*/
		q->key[j+1]=q->key[j];
		q->child[j+1]=q->child[j];
	}
	q->child[1]=q->child[0];
	q->key[1]=p->key[i];
	p->key[i] = '\0';
	q->KeyNum++;
	/*步骤2:将被删结点往左兄弟移*/
	for (j = 1; j <= q->KeyNum; j++){
		aq->key[aq->KeyNum + j] = q->key[j];
		aq->child[aq->KeyNum + j] = q->child[j];
		q->key[j] = '\0';
		q->child[j] = NULL;
		count++;
	}
	aq->KeyNum += count;
	q->KeyNum -= count;
	/*步骤3: 将根结点左移*/
	for (j = i + 1; j <= p->KeyNum; j++){
		p->key[j - 1] = p->key[j];
		p->child[j - 1] = p->child[j];
	}
	p->KeyNum--;
	free(q);
}
/*k关键字已经删除,要调整树,p是关键字k的父结点,i是父结点走向k所在结点的孩子指针数*/
void AdjustBTree(BTNode *p, int i){
	if (i == 0){												//如果删除的是第一个1个孩子指针
		if (p->child[1]->KeyNum > MIN)		//右孩子可以借
			MoveLeft(p,i);				
		else{
			CombineRight(p, i);					//不能借,只能合并
		}
	}
	else if (i == p->KeyNum)								//如果删除的是最后一个孩子指针
	{
		if (p->child[i - 1]->KeyNum > MIN){ //左孩子可借
			MoveRight(p, i);
		} 
		else
		{
			CombineLeft(p, i);					//都不能借,只能合并
		}
	}
	else												//如果删除的是中间部分的关键字
	{
		if (p->child[i - 1]->KeyNum > MIN){//左孩子可借
			MoveRight(p, i);
		}
		else if (p->child[i+1]->KeyNum > MIN){//右孩子可借
			MoveLeft(p, i);
		}
		else{
			CombineRight(p, i);                //都不可以借 只能合并
		}
	}
}

int BTNodeDelete(BTNode *p, KeyType k){
	//在结点p中查找并删除关键字k
	int i;
	int found_tag;                                  //查找标志 
	if (p == NULL)
		return 0;
	else{
		found_tag = FindKeyTree(p, i, k);                //返回查找结果 
		if (found_tag == 1)//查找函数返回为1时就是,查找成功  i值就是该删除关键字的位置
		{                          
			if (p->child[i] != NULL){                  //删除的是非叶子结点
				SubstitutionRight(p, i);                  //寻找相邻关键字(右子树中最小的关键字) 
				BTNodeDelete(p->child[i], p->key[i]);  //执行删除操作 
			}
			else if (p->child[i-1] != NULL){
				SubstitutionLeft(p,i);
				BTNodeDelete(p->child[i],p->key[i]);
			}
			else{
				ReMove(p, i);                        //从结点p中位置i处删除关键字
			}
				
		}
		else
			found_tag = BTNodeDelete(p->child[i], k);    //沿孩子结点递归查找并删除关键字k

		if (p->child[i] != NULL)/*因为上面进入了循环,所以这里每次都会调整一次*/
			if (p->child[i]->KeyNum<MIN)               //删除后关键字个数小于MIN
				AdjustBTree(p, i);                   //调整B树  这里是被删关键词的父结点
		return found_tag;
	}
}
void BTreeDelete(BTree &t, KeyType k){
	//构建删除框架,执行删除操作  
	BTNode *p;
	int a = BTNodeDelete(t, k);                        //删除关键字k 
	if (a == 0)                                        //查找失败 
		printf("   关键字%d不在B树中\n", k);
	else if (t->KeyNum == 0){                          //调整 
		p = t;
		t = t->child[0];
		free(p);
	}
}

void main()
{
	BTree tree=NULL;
	Result s;
	int fre,i=0;
	cout << "请输入决定输入多少个关键字"<< endl;
	cin >> fre;

	while (i<fre){
		int k;
		cout << "输入第"<<i+1<<"个关键字"<< endl;
		cin >> k;
		s = SerchBTree(tree,k);
		/*如果需要筛掉重复项,加上这个,否则不需要加*/
		while (s.tag == 1){
			cout << "输入的关键字k值重复,请重新输入:" << endl;
			cin >> k;
			s = SerchBTree(tree, k);
		}
		InsertBTree(tree, s.i, k, s.p);
		cout << "实时输出" << endl;
		LNodeList L;
		InitLinkList(L);
		PrintBTree(tree, L, 0, 0);
		i++;
	}
	//PrintTree(tree);//直接输出
	/*LNodeList L;
	InitLinkList(L);
	PrintBTree(tree,L,0,0);*///有序输出
	cout << "*******删除********* " << endl;
	while (tree->KeyNum >= 1)
	{
		int k;
		cout << "请输入需要删除的字"<< endl;
		cin >> k;
		BTreeDelete(tree, k);
		LNodeList L;
		InitLinkList(L);
		PrintBTree(tree, L, 0, 0);
	}
	
	cout << "shanchu " << endl;
	system("pause");
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值