B树与B+树

1 二叉树、多叉树、B树、B+树

  • 多叉树与二叉树对比:

树需要加载到内存中,构建树时,需要进行多次I/O操作。
如果节点非常多,会造成树的高度非常大,降低操作速度。
多叉树:降层高,结点数量变少,查找结点的次数就变少。(磁盘查找)

  • 多叉树与B树的关系

1 多叉树没有约束平衡
2 没有约束每个节点子树的数量
3 遍历的时候数据是有顺序的

  • B+树与B树

1 所有数据存储到叶子节点
2 所有叶子节点通过前后指针链接起来

  • 数据库用B+树不用B树

1 B树只适合随机检索,而B+树同时支持随机检索和顺序检索
2 B+树内部节点不存储数据,只存储索引值,因为B+树一个节点可以存储更多的索引值,降低层数,减少了I/O次数,磁盘读写代价更低,I/O读写次数是影响索引检索效率的最大因素
3 B+树的查询效率更加稳定。B树搜索有可能会在非叶子节点阶数,约靠近根节点的记录查找时间越短,其性能等价于在关键字全集内做一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径相同,导致每一个关键字的查询效率相当
4 B树在在基于范围查询的操作上的性能没有B+树好,因为B+树的叶子节点使用了指针顺序的链接在一起,只要遍历叶子节点就可以实现整棵树的遍历,相比较B+树来说,由于B树的叶子节点是相互独立的,所以对于范围查询,需要从根节点再次出发查询,增加了磁盘I/O操作次数
5 增删文件(节点)时,效率更高,因为B+树的叶子节点包含了所有关键字,并以有序的链表结构存储,这样可很好提高增删效率

一个页4k

2 B树的性质

一颗M阶B树T,满足以下条件:

1 每个节点至少拥有M颗子树
2 根节点至少有两棵子树
3 除了根节点外,其余每个分支节点至少有M/2颗子树
4 所有的叶节点都在同一层上
5 有k颗子树的分支节点则存在k-1个关键字,关键字按照递增顺序进行排序
6 关键字数量满足ceil(M/2)-1<=n<=M-1

3 B树的实现

3.1 数据结构

typedef int KEY_VALUE;
// 结点数据结构
typedef struct _btree_node {
	KEY_VALUE *keys;                // key数组
	struct _btree_node **childrens; // 子节点指针数组
	int num;                        // 拥有子节点
	int leaf;                       // 是否叶子节点,1是,0不是
} btree_node;
// 树数据结构
typedef struct _btree {
	btree_node *root;  // 根节点
	int t;             // 2*t表示阶数
} btree;

3.2 创建/删除结点、创建树

  • 创建结点,初始化结点的数据
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));   // 给key数组申请内存
	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);  // 创建结点作为根节点,根节点是叶子节点leaf=1
	T->root = x;
}

3.3 插入结点

插入结点时几种情况:

1 找到插入的结点,并且未满(插入点都是叶子节点)
2 找到结点,已满:
	    2.1 找内节点已满,内节点分裂
    	2.2 找叶子结点已满,叶子节点分裂

分解出两个基本操作:分裂结点,在不满的节点里插入

  • 分裂节点

root的分裂与其他节点的分裂有一点不同,但是这个基本操作是一样的

void btree_split_child(btree *T, btree_node *x, int i) {
	// 参数:树、分裂节点的父节点、父节点在结点数组中的位置
	int t = T->t;
	btree_node *y = x->childrens[i];                     
	btree_node *z = btree_create_node(t, y->leaf); // 用于拷贝分裂后的右半部分   

	z->num = t - 1;  // 共2t-1个结点,分类后两边各t-1,中间的移到父节点上
	// 1. 把y结点右边的拷贝给z
	int j = 0;
	for (j = 0;j < t-1;j ++) {
		z->keys[j] = y->keys[j+t];
	}
	if (y->leaf == 0) {    // 有子结点,也进行拷贝
		for (j = 0;j < t;j ++) {
			z->childrens[j] = y->childrens[j+t];
		}
	}
    // 2. 把y的左边分割出来
	y->num = t - 1;
	// 3.把中间的结点移动到父节点x里 
	for (j = x->num;j >= i+1;j --) {    // 结点后移
		x->childrens[j+1] = x->childrens[j];
	}
	x->childrens[i+1] = z;  // 中间结点放上去
	// 更新x的key和num
	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;
}
  • 在不满的节点里插入

1 如果是叶子节点直接插入
2 如果是内节点,找到对应子节点,如果子节点满了先分裂在插入。

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;
	// 如果是root结点满了
	if (r->num == 2 * T->t - 1) {
		// 创建一个新的根节点,第一个children指向原来的根节点,就跟其他节点一样了
		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++;  // 如果插入的key大,则在右边插入(下标1),否咋在左边插入(下标0)
		btree_insert_nonfull(T, node->childrens[i], key);
	} else {
	// 如果根节点没满,按照非满插入,
		btree_insert_nonfull(T, r, key);
	}
}

3.4 删除数据

删除节点后可能不满足可能会不满足B树的性质,至少M/2颗子树。

判断子树key数量是不是M/2-1

如果相邻子树都是M/2-1,合并。
如果左边子树大于M/2-1,借一个结点。
如果右边子树大于M/2-1,借一个结点。

4 B+树应用场景

B+树主要用在磁盘文件组织、数据索引和数据库索引。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值