数据结构 ------ B树

一,B树的基本概念

那个啥,这篇文章的下面的代码风格,{} 这俩玩意我不知道为啥,它全部自动变成另一种风格了,可是我写的时候并不是这样写的,奇怪,离谱。我也懒得挨个改了,所以代码看起来会有点挫。

假设定义该B树为 m 阶(order)B树。

  • 如果根节点不是叶子节点,则至少有两棵子树
  • 每一个节点最多有 m-1个关键字, m个孩子。
  • 所有的叶子节点都位于同一层
  • 除根节点外,每个叶子节点至少有⌈m/2⌉(向下取整)个关键字。

二,B树的定义

typedef struct BTNode {
	int keynum;			//关键字的个数
	int childnum; 		//孩子的个数
	int order;			//阶树
	struct BTNode* parent;		//双亲
	ElemType* keys;		//关键字数组
	struct BTNode** children;		//孩子数组,//结构体数组  · child数组里面每个数据是 BinNode* 所以是 **
} BTNode,*BTree;

1.初始化

初始化没搞懂,后面的基本白扯。

//初始化
void BTreeInit(BTree* T,int order) {
	(*T) = (BTree)malloc(sizeof(BTNode));
	if(*T == NULL) {
		printf("开辟空间失败!\n");
		exit(-1);
	} else {
		(*T) -> keynum = (*T) -> childnum = 0;
		(*T) -> order = order;
		(*T) -> parent = NULL;
		(*T) -> keys = (ElemType*)malloc(sizeof(ElemType) * (order+1) ); //关键字数组得比孩子数组多一个
		(*T) -> children = (BTNode**)malloc(sizeof(BTNode*) * order);
	}

	//初始化关键字和孩子数组
	int i;
	for (i = 0; i < order; i++) {
		(*T) -> keys[i] = 0;
		(*T) -> children[i] = NULL;
	}
	(*T) -> keys[i] = 0;

}

这段代码其中最重要的其实只有关键字数组和孩子数组的大小。
在这里插入图片描述
因为是5阶B树,所以开辟了6个大小的关键字数组,然后1号单元不放数据,孩子数组开辟了五个。然后一直插入数据。
在这里插入图片描述
当新的随着新的数据5的插入,关键字不是最多只能存放 m-1个吗?所以需要进行分裂。
在这里插入图片描述
而具体如何分裂的话下面会有,这里只是说为什么分配这样的大小。

三,B树的插入

在B树的插入之前,得先构造几个函数来辅助。

1.找当前节点合适的索引

啥意思呢,就是在你所传过来的节点中去,找到需要插入的位置。

//找到当前节点合适的索引
int FindSuitIndex(BTree cur, ElemType data) {
	int i;
	for (i = 1; i <= cur -> keynum; i++) {
		if(cur -> keys[i] > data) {
			return i;
		}
	}

	return i;
}

2.找到合适的叶子节点

要插入一个数据,你得先找到合适的节点,再去合适的索引。

//从根节点开始找到和合适的叶子节点
BTree FindSuitLeafNode(BTree T,ElemType data) {
	//如果根节点没有孩子,直接在根节点插入
	if(T -> childnum == 0) {
		return T;
	} else {
		//找到合适的索引之后
		int index = FindSuitIndex(T,data);

		//因为关键字的一号单元未存放数据
		FindSuitLeafNode(T -> children[index - 1], data);
	}
}

这里解释一下为FindSuitLeafNode(T -> children[index - 1], data); 这样的一个递归,观察下图,假设我现在需要插入一个 2.5 的数据,如果他没有孩子,那么直接在在3的前面插入就行,可是它有孩子,所以我的找到合适索引发现是 1 ,然后对应的下边 则是 1-1 = 0 的位置才可以,才能正好对应上这种从小到大的结构。
在这里插入图片描述

3.添加数据

(1)直接插入

	//在当前的节点上去添加数据
void AddData(BTree* T,BTree cur,ElemType data) {
	//在当前节点上找到合适的索引
	int index = FindSuitIndex(cur,data);
	int i;
	for (i = cur -> keynum; i >= index; i--) {
		cur -> keys[i+1] = cur -> keys[i];
	}
	cur -> keys[index] = data;
	cur -> keynum++;

for 循环呢是为了把更小的数据插入到合适的位置,把大的数据向后移动。数组中最基本的操作了。

(2)分裂

当你发现关键字的个数等于了B树的阶数的时候,就该分裂了。

①分裂关键字
	//关键字的个数最大是 阶数减一
	//不满足条件则进行分裂
	if(cur -> keynum == cur -> order) {

		BTree lchild = (BTree)malloc(sizeof(BTNode));
		BTree rchild = (BTree)malloc(sizeof(BTNode));
		BTreeInit(&lchild,cur -> order);
		BTreeInit(&rchild,cur -> order);
		int mid = cur -> order / 2 + cur -> order % 2; 	//算中间下标的公式

		int i;
		//1. 分裂关键字, mid 位置所对应的关键字不进行处理
		for (i = 1; i < mid; i++) {
			//左边的关键去左孩子上
			AddData(T,lchild,cur -> keys[i]);
		}

		for (i = mid+1; i <= cur -> keynum; i++) {
			//右边的关键字去右边
			AddData(T,rchild,cur -> keys[i]);
		}

递归调用,因为加入新的数后,可能还会引起一系列的分裂。
在这里插入图片描述

②分裂当前节点所对应的孩子
	//2. 分裂当前节点所对应的孩子们
		for (i = 0; i < mid; i++) {
			//把当前节点左边的孩子们给新的孩子带过去
			lchild -> children[i] = cur -> children[i];
			//如果真的有值
			if(cur -> children[i] != NULL) {
				//与父亲链接
				cur -> children[i] -> parent = lchild;
				lchild -> childnum++;
			}
		}

		//右边的新孩子的下标得从 0 开始
		int j = 0;
		for ( i = mid; i < cur -> childnum; i++) {
			rchild -> children[j] = cur -> children[i];
			if(cur -> children[i] != NULL) {
				//与父亲链接
				cur -> children[i] -> parent = rchild;
				rchild -> childnum++;
				j++;
			}
		}
③生成新的父亲节点

如果没有父亲节点,那么将mid所对应的值直接提升为父亲节点即可。

//3.生成新的父亲节点
		if(cur -> parent == NULL) {
			BTree newparent= (BTree)malloc(sizeof(BTNode));
			BTreeInit(&newparent,cur -> order);


			//AddData(T,newparent,cur -> keys[mid]);

			newparent -> children[0] = lchild;
			newparent -> children[1] = rchild;
			lchild -> parent = newparent;
			rchild -> parent = newparent;
			newparent -> childnum = 2;
			
			AddData(T,newparent,cur -> keys[mid]);

			*T = newparent;
		} else {
			//如果由父亲节点
			//分裂出去的孩子的父亲还是我原来的父亲
			lchild -> parent = rchild -> parent = cur -> parent;

			//将我提上去
			//先找到自己在父亲节点中合适的位置
			index = FindSuitIndex(cur -> parent,cur -> keys[mid]);

			//左孩子可以直接链接
			cur -> parent -> children[index - 1] = lchild;
			//右孩子则需要判断原来索引所对应的孩子的位置是否有值
			if(cur -> parent -> children[index] != NULL) {
				//开始向后挪动
				for (i = cur -> parent -> childnum -1; i >= index ; i--) {
					cur -> parent -> children[i+1] = cur -> parent -> children[i];
				}
			}
			//挪完之后赋值
			cur -> parent -> children[index] = rchild;

			//由原来的自己本身的一个孩子 变成了 俩 所以还是加了一个
			cur -> parent -> childnum++;

			//最后将 mid 所对应的关键字插入到 父亲节点中去
			AddData(T,cur -> parent, cur -> keys[mid]);
		}
	}

}

主要看第二种吧。
比如树的原型是这样的。
在这里插入图片描述
然后我连续插入几个数据之后发现得进行分裂。那么当前节点分裂出来的两个孩子的父亲,跟我原来的父亲是一样的,
在这里插入图片描述
index = 2
(index -1 ) 所对应的则是左孩子。 跟最开始所讲的FIndISuitLeafNode中的原理一样。
(index) 所对应的则是右孩子。
如果像图1一样没有 7,8.这个节点那么直接就可以链接起来,但是图2才是需要解决的,那么我们只需要将原来的往后挪动即可。

在这里插入图片描述

4. 构造插入函数

插入函数就这两句话,找到合适的节点,然后插入数据就好了。

//在B-树中插入数据
void BTreeInsert(BTree* T,ElemType data) {

	//首先找到合适的节点
	BTree cur = FindSuitLeafNode(*T,data);

	//在合适的节点中去插入数据
	AddData(T,cur,data);

}
1

四,结尾

最开始我也不会B树,头大,但是一点点的尝试,总归会实现的。
我也是在学习中,学习别人的代码,学习新的知识,这篇博客的代码学习来自于B站上的一位视频博主。
视频链接
源码链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值