B-树与B+树原理及实现


前言

本文主要讲:
(1)B-树定义与性质;
(2)B-树的实现(查找,插入,删除)
(3)B+树的定义与性质


提示:以下是本篇文章正文内容,下面案例可供参考

一、B-树的定义及解释

估计很多朋友在开始看B-树(读B树,不是B减树)的时候,一上来就是连续的定义,直接给看懵。所以这里我会将B树的定义拆分解释,以便读者理解。我们先来看严蔚敏书上对B树的定义是这样描述的:
一棵m阶的B-树,或为空树,或为满足下列特性的m叉树

这里就得暂停以下给大家解释解释什么叫做B树的阶。B树的阶并不是由树本身决定的,而是人为先确定的,很多地方没有写清楚,常常把已有的一棵B树中分支子树最多的分支数当做B数的阶,这其实是不对的,一棵4阶的B树,可能子树的分支数最多为3,但并不意味着B树就为3阶,一般只有一些题目没告诉你阶数的时候才只能把最大分支数当阶数。例如如下一棵5阶B树,它的子结点最多只有3个分支,但我们不能说它就是个3阶B树,B树较为准确的画法就是将其key值数组画出来,即使不满。那么B数的阶就是数组长度加1。(这里没把n值画出来)
图1
我们接着来看B-树的性质。
(1)树中每个结点至多有m棵子树;
(2)若根节点不是叶子节点,则至少有两棵子树;

这比较容易理解,比如上述5阶B树,每个节点的子树最多的时候,就是key值全满的时候,每个key值能引出两个分叉,相邻的Key值分叉一样,所以总共五个分叉,即5棵子树;根节点不是叶子节点时,就说明至少有一个Key值,所以至少有两棵子树。需要注意的一点是,子树,或者说子结点,并不是图中的数字所在的节点,那个只是当前节点包含的信息,一般叫做key值,关键字。一个结点中,应包含关键字信息,子结点的指针,以及关键字个数,这也是我们要将讲的第四个性质。我们这里先介绍性质(4),再介绍性质(3)。
(4)所有非终端结点中包含下列信息数据
(n, A0,K1, A2, K2, … , Kn, An)

n就是当前结点k值的个数,比如上图包含key值为30的结点,n等于1,下面两个子结点的n值都等于2,A0到An都是指向子结点的指针,也即子结点的地址。k1 … Kn就是关键字。
这其实意味着,我们定义一棵B树的时候,至少应该有两个数组,一个用来存key值,一个用来存子树的地址,并且还需要一个变量来指示key值的个数。具体的实现我们会在后面给出,我们接着来看性质(4)剩下的部分:
其中Ki为关键字,且Ki < Ki+1,Ai为指向子树根结点的指针,且指针Ai-1所指子树中所有结点的关键字均小于Ki,An中所指子树的所有结点的关键字均大于Kn。
这句话的思就是,在一个结点中,key值从左到右(或从右到左也行,一般我们从左到右)是逐渐增大的,并且,一个key值左边的指针Ai-1所指向的结点中所有Key值都小于当前key,比如30的左边的子树中key值都小于key,右边子树的结点中的key值都大于key。An是最右边的指针,所以其所指结点的关键字肯定都大于Kn。
接着还有
n(ceil[m/2] - 1 <= n <= m-1)为关键字的个数,或n+1为子树的个数
这句话的意思就是,一个m阶的B-树,它的关键字个数(注意前提,非终端结点)满足上式。比如一个5阶B-树,非终端结点的key值个数大等于2小等于4。这时由性质3决定的,性质3如下:

(3)除根之外的所有非终端节点至少有ceil[m/2]棵子树,ceil是取上限,即5阶B-树,5/2等于2.5,取上限等于3,即除根节点外,每个非终端结点至少有3棵子树,也就意味着key的个数至少是2。事实上我们也发现了,key值的个数等于子树的个数减一,所以上述n的范围就是这么来的。
5)所有叶子节点都出现在同一层次上,并且不带信息(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)
上图没把叶子节点画出来,这个其实看每个人的实现,就像红黑树有的人直接用NULL来作为叶子节点,有的增加一个哨兵节点,具体理解就结合下面代码来理解就好了。

二、B-树的查找,插入(创建),删除

在实现树的操作前,我们先来定义树的结构如下:

typedef int KEY_VALUE;

typedef struct _btree_node {
	KEY_VALUE *keys;
	struct _btree_node **childrens;
	//key的个数,一般我们也常用keys[0]来存储key的个数
	int num;
	//指示是否叶子结点,0,否,1,是
	int leaf;
} btree_node;

typedef struct _btree {
	btree_node *root;
	//t = ceil[m/2]
	int t;
} btree;

1.查找

图2

我们说B树的查找,都是指给定key值,从B树中查找该Key。
我们从根节点开始,(对每个结点中的key值查找方法很多,可以从左往右找,也可以从右往左,因为key值是顺序的,用二分查找平均效率也会高些)。假如,我们要查找key = 40,从根结点开始,根节点就一个key,大于30,因此就往右子结点找,因为右子大于39,小于45,因此顺着中间的子结点找,就找到了40这个Key值,那么如果从根到叶子结点都没找到,我们就认为该Key不存在。查找的代码实现如下,这里使用二分查找:

//low一般为1,high为n
//这里仅是一个结点的查找方法,外层再加一个从根到叶子节点的查找while(node->leaf != 1)即可
int btree_bin_search(btree_node *node, int low, int high, KEY_VALUE key) {
	int mid;
	if (low > high || low < 0 || high < 0) {
		return -1;
	}

	while (low <= high) {
		mid = (low + high) / 2;
		if (key > node->keys[mid]) {
			low = mid + 1;
		} else {
			high = mid - 1;
		}
	}
	//假如key值大于最于Kn,那么返回n值,以便从An所指的结点继续查找,假如小于A0,返回的为0,从A0所指的结点继续查找,直到叶子节。
	return low;
}

2.B-树的插入

B树的插入始终是从叶子结点插入,什么意思呢,我们拿到一个key值的结点,要将它插入到已有的B-树中,依然是顺着根节点往下走,找到待插入的位置。比如我们要插入的结点的key值是43,我们顺着往下找发觉应该插在key值等于42所在的结点处,这是待插入的位置所在的结点的子树个数/key值个数还未超出的情况。有一种情况比较特殊,如上图47所在的结点,其结点k值个数已经到最大值了,那么假如我们要再插入一个60,这时直接插入后就不满足B-树的定义了。怎么办呢,我们这时候就需要将该结点分裂(注意这里是先分裂,才能插入)。分裂的方法如下:取当前节点中的中间结点,作为新结点,往上一层结点中插入,若上一层结点数也满了,则继续分裂直至根结点,若根结点也满了,则按此方法得到的新结点作为根节点。
在说插入一个结点前,我们先来把创建一个新结点的方法给介绍了:

btree_node *btree_create_node(int t, int leaf) {
	
	btree_node *node = (btree_node*)calloc(1, sizeof(btree_node));
	if (node == NULL) assert(0);
	
	node->leaf = leaf;
	//这里t = ceil[m/2],即一个m阶的B树的话,则开辟ceil[m/2] * 2大小的空间。之所以这样,是因为
	//我们判断时,是要先插入,再判断数目是否达到,比如对于一个5阶的B数,t=3。2t = 6,我们插入的时候
	//是会将第六个分支,也即第五个key值插入后,再分裂的。所以你开的key的空间大小,不能小于5,不然不就无法插入了,
	
	node->keys = (KEY_VALUE*)calloc(1, (2*t-1)*sizeof(KEY_VALUE));
	node->childrens = (btree_node**)calloc(1, (2*t) * sizeof(btree_node));
	node->num = 0;

	return node;
}
//插入过程中分裂的处理
void btree_split_child(btree *T, btree_node *x, int i) {
	int t = T->t;
	//分裂后一个结点变成两个结点,所以需要再创建一个新结点z
	//用来存储分裂后的另一个结点的信息,其叶子属性与原结点y相同
	btree_node *y = x->childrens[i];
	btree_node *z = btree_create_node(t, y->leaf);

	//分裂后新结点key值数为[m/2] - 1
	z->num = t - 1;

	int j = 0;
	for (j = 0;j < t-1;j ++) {
		//原y的右半部分key赋值给z
		z->keys[j] = y->keys[j+t];
	}
	if (y->leaf == 0) {
		//如果不是叶子节点,有子树,子树的地址也要复制一份
		for (j = 0;j < t;j ++) {
			z->childrens[j] = y->childrens[j+t];
		}
	}
	//分出去后,y的key值个数也变成t-1
	y->num = t - 1;
	for (j = x->num;j >= i+1;j --) {
		x->childrens[j+1] = x->childrens[j];
	}
	//父节点的A[i+1]位置的指针赋值给新的z(注意这里父节点必然是非满的)
	
	x->childrens[i+1] = z;
	//i+1的位置给了z,原父节点的key值对应挪动
	for (j = x->num-1;j >= i;j --) {
		x->keys[j+1] = x->keys[j];
	}
	x->keys[i] = y->keys[t-1];
	x->num += 1;
	
}

//往一个非空的结点中插入结点,这里key值数组的下标从0开始。
void btree_insert_nonfull(btree *T, btree_node *x, KEY_VALUE k) {

	int i = x->num - 1;

	if (x->leaf == 1) {
		//若当前插入结点为叶子节点,找到当前节点的中插入的位置,实际上就是一个直接插入排序的过程
		while (i >= 0 && x->keys[i] > k) {
			x->keys[i+1] = x->keys[i];
			i --;
		}
		//找到位置后,插入该位置,并将当前节点的key值数目加1
		x->keys[i+1] = k;
		x->num += 1;
		
	} else {
		//若当前插入结点为非叶子结点(在往上层插入过程中会出现的情况)
		//找到要插入的A0的位置,即找到keys[i] < l <keys[i+1],这时找到位置后,就要顺着A[i+1]指向
		//的子结点继续判断(递归的方式)
		//即children[i+1]
		while (i >= 0 && x->keys[i] > k) i --;
		//但在插入前,要先判断插入的子树是否已经满了,如果已经满了需要先分裂,也就是说,我们在插入上一个结点时
		//结点已经满了,但我们当时并不分裂,而是等到下一次插入的时候,判断已经满了,再分裂,分裂后再插入
		if (x->childrens[i+1]->num == (2*(T->t))-1) {
			btree_split_child(T, x, i+1);
			if (k > x->keys[i+1]) i++;
		}
		//假如当前不是叶子结点,就往当前结点的子结点里面插
		btree_insert_nonfull(T, x->childrens[i+1], k);
	}
}

void btree_insert(btree *T, KEY_VALUE key) {
	//int t = T->t;

	btree_node *r = T->root;
	//若当前根结点已经满了,此时需要分裂,创建新的根结点
	if (r->num == 2 * T->t - 1) {
		
		btree_node *node = btree_create_node(T->t, 0);
		T->root = node;
		//原根结点作为现在根结点的A0子结点
		node->childrens[0] = r;
		//node是父节点,需要分裂的是其子结点
		btree_split_child(T, node, 0);

		int i = 0;
		if (node->keys[0] < key) i++;
		btree_insert_nonfull(T, node->childrens[i], key);
		
	} else {
		btree_insert_nonfull(T, r, key);
	}
}

3.B-树的删除

B树的删除也会分情况讨论,因为B树的关键字个数需满足n(ceil[m/2] - 1 <= n <= m-1),当删除后n值不满足时,我们就需要分情况讨论了,因此,整个删除分以下几个场景:
1、当删除的结点是非终端节点时,需要查找替换关键字,若其左子树及右子树的关键字个数都删除后都满足,则取左子树的最大关键字或右子树的最大关键字作为替换,若只有一个满足,取满足的子树的对应关键字作为替换,若都不满足,也取左子树的最大关键字或右子树的最大关键字作为替换,删除后做合并。即转换为场景2的第三种
2、当删除的是终端节点时,
(1)删除节点的关键字数满足,直接删除关键字;
(2)关键字节点的关键字数删除后不满足,若其兄弟节点存在删除后满足的关键字个数,则将兄弟结点中的替换结点作为删除节点,这过程要涉及到删除节点的父节点的key值的替换。
(3)若关键字结点的关键字数删除后不满足且不能向兄弟结点借的时候,直接删除当前节点,做合并。
合并的方法,一般遵循一个原则,与关键字数目较少的结点合并,(父节点的关键字下移合并)。若合并后,父节点的关键字数目不满足,则继续往上合并。

代码实现如下:

void btree_merge(btree *T, btree_node *node, int idx) {

	btree_node *left = node->childrens[idx];
	btree_node *right = node->childrens[idx+1];

	int i = 0;

	/data merge
	left->keys[T->t-1] = node->keys[idx];
	for (i = 0;i < T->t-1;i ++) {
		left->keys[T->t+i] = right->keys[i];
	}
	if (!left->leaf) {
		for (i = 0;i < T->t;i ++) {
			left->childrens[T->t+i] = right->childrens[i];
		}
	}
	left->num += T->t;

	//destroy right
	btree_destroy_node(right);

	//node 
	for (i = idx+1;i < node->num;i ++) {
		node->keys[i-1] = node->keys[i];
		node->childrens[i] = node->childrens[i+1];
	}
	node->childrens[i+1] = NULL;
	node->num -= 1;

	if (node->num == 0) {
		T->root = left;
		btree_destroy_node(node);
	}
}

void btree_delete_key(btree *T, btree_node *node, KEY_VALUE key) {

	if (node == NULL) return ;

	int idx = 0, i;

	while (idx < node->num && key > node->keys[idx]) {
		idx ++;
	}

	if (idx < node->num && key == node->keys[idx]) {

		if (node->leaf) {
			
			for (i = idx;i < node->num-1;i ++) {
				node->keys[i] = node->keys[i+1];
			}

			node->keys[node->num - 1] = 0;
			node->num--;
			
			if (node->num == 0) { //root
				free(node);
				T->root = NULL;
			}

			return ;
		} else if (node->childrens[idx]->num >= T->t) {

			btree_node *left = node->childrens[idx];
			node->keys[idx] = left->keys[left->num - 1];

			btree_delete_key(T, left, left->keys[left->num - 1]);
			
		} else if (node->childrens[idx+1]->num >= T->t) {

			btree_node *right = node->childrens[idx+1];
			node->keys[idx] = right->keys[0];

			btree_delete_key(T, right, right->keys[0]);
			
		} else {

			btree_merge(T, node, idx);
			btree_delete_key(T, node->childrens[idx], key);
			
		}
		
	} else {

		btree_node *child = node->childrens[idx];
		if (child == NULL) {
			printf("Cannot del key = %d\n", key);
			return ;
		}

		if (child->num == T->t - 1) {

			btree_node *left = NULL;
			btree_node *right = NULL;
			if (idx - 1 >= 0)
				left = node->childrens[idx-1];
			if (idx + 1 <= node->num) 
				right = node->childrens[idx+1];

			if ((left && left->num >= T->t) ||
				(right && right->num >= T->t)) {

				int richR = 0;
				if (right) richR = 1;
				if (left && right) richR = (right->num > left->num) ? 1 : 0;

				if (right && right->num >= T->t && richR) { //borrow from next
					child->keys[child->num] = node->keys[idx];
					child->childrens[child->num+1] = right->childrens[0];
					child->num ++;

					node->keys[idx] = right->keys[0];
					for (i = 0;i < right->num - 1;i ++) {
						right->keys[i] = right->keys[i+1];
						right->childrens[i] = right->childrens[i+1];
					}

					right->keys[right->num-1] = 0;
					right->childrens[right->num-1] = right->childrens[right->num];
					right->childrens[right->num] = NULL;
					right->num --;
					
				} else { //borrow from prev

					for (i = child->num;i > 0;i --) {
						child->keys[i] = child->keys[i-1];
						child->childrens[i+1] = child->childrens[i];
					}

					child->childrens[1] = child->childrens[0];
					child->childrens[0] = left->childrens[left->num];
					child->keys[0] = node->keys[idx-1];
					
					child->num ++;

					node->keys[idx-1] = left->keys[left->num-1];
					left->keys[left->num-1] = 0;
					left->childrens[left->num] = NULL;
					left->num --;
				}

			} else if ((!left || (left->num == T->t - 1))
				&& (!right || (right->num == T->t - 1))) {

				if (left && left->num == T->t - 1) {
					btree_merge(T, node, idx-1);					
					child = left;
				} else if (right && right->num == T->t - 1) {
					btree_merge(T, node, idx);
				}
			}
		}

		btree_delete_key(T, child, key);
	}
	
}


int btree_delete(btree *T, KEY_VALUE key) {
	if (!T->root) return -1;

	btree_delete_key(T, T->root, key);
	return 0;
}

完整版的代码如下:



#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <assert.h>

#define DEGREE		3
typedef int KEY_VALUE;

typedef struct _btree_node {
	KEY_VALUE *keys;
	struct _btree_node **childrens;
	int num;
	int leaf;
} btree_node;

typedef struct _btree {
	btree_node *root;
	int t;
} btree;

btree_node *btree_create_node(int t, int leaf) {

	btree_node *node = (btree_node*)calloc(1, sizeof(btree_node));
	if (node == NULL) assert(0);

	node->leaf = leaf;
	node->keys = (KEY_VALUE*)calloc(1, (2*t-1)*sizeof(KEY_VALUE));
	node->childrens = (btree_node**)calloc(1, (2*t) * sizeof(btree_node));
	node->num = 0;

	return node;
}

void btree_destroy_node(btree_node *node) {

	assert(node);

	free(node->childrens);
	free(node->keys);
	free(node);
	
}


void btree_create(btree *T, int t) {
	T->t = t;
	
	btree_node *x = btree_create_node(t, 1);
	T->root = x;
	
}


void btree_split_child(btree *T, btree_node *x, int i) {
	int t = T->t;
	//分裂后一个结点变成两个结点,所以需要再创建一个新结点z
	//用来存储分裂后的另一个结点的信息,其叶子属性与原结点y相同
	btree_node *y = x->childrens[i];
	btree_node *z = btree_create_node(t, y->leaf);

	//分裂后新结点key值数为[m/2] - 1
	z->num = t - 1;

	int j = 0;
	for (j = 0;j < t-1;j ++) {
		//原y的右半部分key赋值给z
		z->keys[j] = y->keys[j+t];
	}
	if (y->leaf == 0) {
		//如果不是叶子节点,有子树,子树的地址也要复制一份
		for (j = 0;j < t;j ++) {
			z->childrens[j] = y->childrens[j+t];
		}
	}
	//分出去后,y的key值个数也变成t-1
	y->num = t - 1;
	for (j = x->num;j >= i+1;j --) {
		x->childrens[j+1] = x->childrens[j];
	}
	//父节点的A[i+1]位置的指针赋值给新的z(注意这里父节点必然是非满的)
	
	x->childrens[i+1] = z;
	//i+1的位置给了z,原父节点的key值对应挪动
	for (j = x->num-1;j >= i;j --) {
		x->keys[j+1] = x->keys[j];
	}
	x->keys[i] = y->keys[t-1];
	x->num += 1;
	
}

void btree_insert_nonfull(btree *T, btree_node *x, KEY_VALUE k) {

	int i = x->num - 1;

	if (x->leaf == 1) {
		//如果当前是往叶子
		while (i >= 0 && x->keys[i] > k) {
			x->keys[i+1] = x->keys[i];
			i --;
		}
		x->keys[i+1] = k;
		x->num += 1;
		
	} else {
		while (i >= 0 && x->keys[i] > k) i --;

		if (x->childrens[i+1]->num == (2*(T->t))-1) {
			btree_split_child(T, x, i+1);
			if (k > x->keys[i+1]) i++;
		}

		btree_insert_nonfull(T, x->childrens[i+1], k);
	}
}

void btree_insert(btree *T, KEY_VALUE key) {
	//int t = T->t;

	btree_node *r = T->root;
	if (r->num == 2 * T->t - 1) {
		
		btree_node *node = btree_create_node(T->t, 0);
		T->root = node;

		node->childrens[0] = r;

		btree_split_child(T, node, 0);

		int i = 0;
		if (node->keys[0] < key) i++;
		btree_insert_nonfull(T, node->childrens[i], key);
		
	} else {
		btree_insert_nonfull(T, r, key);
	}
}

void btree_traverse(btree_node *x) {
	int i = 0;

	for (i = 0;i < x->num;i ++) {
		if (x->leaf == 0) 
			btree_traverse(x->childrens[i]);
		printf("%C ", x->keys[i]);
	}

	if (x->leaf == 0) btree_traverse(x->childrens[i]);
}

void btree_print(btree *T, btree_node *node, int layer)
{
	btree_node* p = node;
	int i;
	if(p){
		printf("\nlayer = %d keynum = %d is_leaf = %d\n", layer, p->num, p->leaf);
		for(i = 0; i < node->num; i++)
			printf("%c ", p->keys[i]);
		printf("\n");
#if 0
		printf("%p\n", p);
		for(i = 0; i <= 2 * T->t; i++)
			printf("%p ", p->childrens[i]);
		printf("\n");
#endif
		layer++;
		for(i = 0; i <= p->num; i++)
			if(p->childrens[i])
				btree_print(T, p->childrens[i], layer);
	}
	else printf("the tree is empty\n");
}


int btree_bin_search(btree_node *node, int low, int high, KEY_VALUE key) {
	int mid;
	if (low > high || low < 0 || high < 0) {
		return -1;
	}

	while (low <= high) {
		mid = (low + high) / 2;
		if (key > node->keys[mid]) {
			low = mid + 1;
		} else {
			high = mid - 1;
		}
	}

	return low;
}


//{child[idx], key[idx], child[idx+1]} 
void btree_merge(btree *T, btree_node *node, int idx) {

	btree_node *left = node->childrens[idx];
	btree_node *right = node->childrens[idx+1];

	int i = 0;

	/data merge
	left->keys[T->t-1] = node->keys[idx];
	for (i = 0;i < T->t-1;i ++) {
		left->keys[T->t+i] = right->keys[i];
	}
	if (!left->leaf) {
		for (i = 0;i < T->t;i ++) {
			left->childrens[T->t+i] = right->childrens[i];
		}
	}
	left->num += T->t;

	//destroy right
	btree_destroy_node(right);

	//node 
	for (i = idx+1;i < node->num;i ++) {
		node->keys[i-1] = node->keys[i];
		node->childrens[i] = node->childrens[i+1];
	}
	node->childrens[i+1] = NULL;
	node->num -= 1;

	if (node->num == 0) {
		T->root = left;
		btree_destroy_node(node);
	}
}

void btree_delete_key(btree *T, btree_node *node, KEY_VALUE key) {

	if (node == NULL) return ;

	int idx = 0, i;

	while (idx < node->num && key > node->keys[idx]) {
		idx ++;
	}

	if (idx < node->num && key == node->keys[idx]) {

		if (node->leaf) {
			
			for (i = idx;i < node->num-1;i ++) {
				node->keys[i] = node->keys[i+1];
			}

			node->keys[node->num - 1] = 0;
			node->num--;
			
			if (node->num == 0) { //root
				free(node);
				T->root = NULL;
			}

			return ;
		} else if (node->childrens[idx]->num >= T->t) {

			btree_node *left = node->childrens[idx];
			node->keys[idx] = left->keys[left->num - 1];

			btree_delete_key(T, left, left->keys[left->num - 1]);
			
		} else if (node->childrens[idx+1]->num >= T->t) {

			btree_node *right = node->childrens[idx+1];
			node->keys[idx] = right->keys[0];

			btree_delete_key(T, right, right->keys[0]);
			
		} else {

			btree_merge(T, node, idx);
			btree_delete_key(T, node->childrens[idx], key);
			
		}
		
	} else {

		btree_node *child = node->childrens[idx];
		if (child == NULL) {
			printf("Cannot del key = %d\n", key);
			return ;
		}

		if (child->num == T->t - 1) {

			btree_node *left = NULL;
			btree_node *right = NULL;
			if (idx - 1 >= 0)
				left = node->childrens[idx-1];
			if (idx + 1 <= node->num) 
				right = node->childrens[idx+1];

			if ((left && left->num >= T->t) ||
				(right && right->num >= T->t)) {

				int richR = 0;
				if (right) richR = 1;
				if (left && right) richR = (right->num > left->num) ? 1 : 0;

				if (right && right->num >= T->t && richR) { //borrow from next
					child->keys[child->num] = node->keys[idx];
					child->childrens[child->num+1] = right->childrens[0];
					child->num ++;

					node->keys[idx] = right->keys[0];
					for (i = 0;i < right->num - 1;i ++) {
						right->keys[i] = right->keys[i+1];
						right->childrens[i] = right->childrens[i+1];
					}

					right->keys[right->num-1] = 0;
					right->childrens[right->num-1] = right->childrens[right->num];
					right->childrens[right->num] = NULL;
					right->num --;
					
				} else { //borrow from prev

					for (i = child->num;i > 0;i --) {
						child->keys[i] = child->keys[i-1];
						child->childrens[i+1] = child->childrens[i];
					}

					child->childrens[1] = child->childrens[0];
					child->childrens[0] = left->childrens[left->num];
					child->keys[0] = node->keys[idx-1];
					
					child->num ++;

					node->keys[idx-1] = left->keys[left->num-1];
					left->keys[left->num-1] = 0;
					left->childrens[left->num] = NULL;
					left->num --;
				}

			} else if ((!left || (left->num == T->t - 1))
				&& (!right || (right->num == T->t - 1))) {

				if (left && left->num == T->t - 1) {
					btree_merge(T, node, idx-1);					
					child = left;
				} else if (right && right->num == T->t - 1) {
					btree_merge(T, node, idx);
				}
			}
		}

		btree_delete_key(T, child, key);
	}
	
}


int btree_delete(btree *T, KEY_VALUE key) {
	if (!T->root) return -1;

	btree_delete_key(T, T->root, key);
	return 0;
}


int main() {
	btree T = {0};

	btree_create(&T, 3);
	srand(48);

	int i = 0;
	char key[26] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	for (i = 0;i < 26;i ++) {
		//key[i] = rand() % 1000;
		printf("%c ", key[i]);
		btree_insert(&T, key[i]);
	}

	btree_print(&T, T.root, 0);

	for (i = 0;i < 26;i ++) {
		printf("\n---------------------------------\n");
		btree_delete(&T, key[25-i]);
		//btree_traverse(T.root);
		btree_print(&T, T.root, 0);
	}
	
}


二、B+树

说完了B-树,就来说说B+树,
理解了B树之后再来看B+树就比较简单了
(1)在B+树中,具有n个关键字的结点含有n个分支;这点与B-树不一样,B树中具有n个关键字的结点,含有n+1个分支。
(2)B+树中,每个结点(除根结点外)中的关键字个数n的取值范围为[m/2] <= n <= m,根结点的取值范围为
2<=n<=m;在B-树中,范围是[m/2]-1<=n<=m-1和1<=n<=m-1;
(3)B+树中,叶子节点包含信息,包含全部关键字,叶子节点引出的指针指向记录(存在磁盘上的信息)
(4)B+树中,所有的非叶节点,仅仅起到一个索引的作用,即节点中的每个索引项只含有对应子树的最大关键字和指向该子树的指针。不含有关键字对应的记录的存储地址。而在B-树中,每个关键字对应一个记录的存储地址。
(5)B+树中,有一些指针指向关键字最小的叶子节点,所有叶子节点连接成一个线性表
B+树解决了B-树没办法查询key值在一个范围的情况内的情况,也就是范围查找,比如查找key在3-10内所有的数据
,B-树对每个key值都需要从根节点进行查找。但B+树所有的叶子节点包含了所有的信息。并且是一个顺序表,所以可以做范围查找。这也就是为什么Mysql用的是B+树的原因
该处使用的url网络请求的数据。
B+树的实现这里就不给出了


总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值