目录
一、学习前言
前面我们已经讲解过了二叉树、二叉搜索树(BST)、平衡二叉搜索树(AVL树)、红黑树,接下
爱就让我们了解下B树到底是什么?
二、基本介绍
在计算机科学中,B树是一种自平衡的树形数据结构,能够保持数据有序。
这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数量级的时间复杂度内
完成。
- B树,其实是一颗特殊的二叉查找树(binary search tree),可以拥有多于2个子节点。与自平衡二叉查找树不同,B树为系统大块数据的读写操作做了优化。
- B树减少定位记录时所经历的中间过程,从而加快存取速度,其实B树主要解决的就是数据IO的问题。
- 树这种数据结构可以用来描述外部存储。这种数据结构常被应用在数据库和文件系统的实现上。
总而言之,B树主要用于管理磁盘上的数据管理(减少磁盘IO次数),而之前说的AVL树与红黑树
适合用于内存数据管理。例如:存储一个100w的数据使用AVL存储,树高大约为20层(),如果使用磁盘IO查询20次效率较低。
注意:常常面试官会问B树和B-树的区别,我们该如何回答?其实,B树和B-树是同一种数据结
构,如果不清楚的话,会被面试官忽悠!
三、特性
1. 从概念上说起
度degree:指树中节点孩子数
阶order:指所有节点孩子数中最大值
一棵 B-树具有以下性质
特性1:每个节点 x 具有
- 属性 n,表示节点 x 中 key 的个数
- 属性 leaf,表示节点是否是叶子节点
- 节点 key 可以有多个,以升序存储
特性2:每个节点最多具有m个孩子,其中m叫做B-树的阶
特性3:除根结点与叶子节点外,每个节点至少有ceil(m/2)个孩子,根节点不是叶子节点时,最少
有两个孩子。叶子节点没有孩子
特性4:每个非叶子节点中的孩子数是 n + 1。而n的取值为ceil(m/2)-1<=n<=m-1。
特性5:最小度数t(节点的孩子数称为度)和节点中键数量的关系如下:
最小度数t |
键数量范围 |
2 |
1 ~ 3 |
3 |
2 ~ 5 |
4 |
3 ~ 7 |
... |
... |
n |
(n-1) ~ (2n-1) |
其中,当节点中键数量达到其最大值时,即 3、5、7 ... 2n-1,需要分裂
特性6:叶子节点的深度都相同
2. 举个例子
一个m阶的B树特点如下:
- 所有叶子节点都在同一层级;
- 除了根节点以外的其他节点包含的key值数量在[m/2]-1到m-1的数据范围;
- 除了根节点和叶子节点外,所有中间节点至少有m/2个孩子节点;
- 根节点如果不是叶子节点的话,它必须包含至少2个孩子节点;
- 拥有n-1个key值非叶子节点必须有n个孩子节点;
- 一个节点的所有key值必须是升序排序的;
以上六点就是B树的全部特性
四、代码实现
节点准备
B树的节点属性,与其他树不太相同,首先是key可以有多个,因此要设置为数组,孩子节点也未
知,因此也要设置为数组。
本应该还存在一个value属性,这里简化掉,不添加该属性。
static class Node {
boolean leaf = true; //是否是叶子节点
int keyNumber; //有效key
int t; //最小度
int[] keys; // keys数组
Node[] children; //孩子节点数组
public Node(int t) {
this.t = t;
this.keys = new int[2 * t - 1];
this.children = new Node[2 * t];//最大孩子节点个数为为2*t
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));
}
/**
* 根据key获取对应节点
*
* @param key
* @return
*/
Node get(int key) {
int i = 0;
while (i < keyNumber) {
//如果在该节点找到,那么直接返回即可
if (keys[i] == key) {
return this;
}
//说明要找的元素可能在children[i]中
if (keys[i] > key) {
break;
}
i++;
}
//如果是叶子节点,直接返回null
if (leaf) {
return null;
}
return children[i].get(key);
}
/**
* 指定位置插入元素
*
* @param key
* @param index
*/
void insertKey(int key, int index) {
System.arraycopy(keys, index, keys, index + 1, keyNumber - index);
keys[index] = key;
keyNumber++;
}
/**
* 向节点中插入孩子节点
* @param child
* @param index
*/
void insertChild(Node child, int index) {
System.arraycopy(children, index, children, index + 1, keyNumber - index);
children[index] = child;
}
}
这里我采用了静态数组,因此需要多添加一个keyNumber参数来获取有效key的数量,如果使用
ArrayList,可以通过size方法获取,因此不需要添加这个属性。
大体框架
public class BTree {
private Node root;
private int t;//最小度数
final int MAX_KEY_NUMBER;//最大key数量
final int MIN_KEY_NUMBER;//最小key数量。用于分裂使用
public BTree(int t) {
this.t = t;
root = new Node(t);
MAX_KEY_NUMBER = 2 * t - 1;
MIN_KEY_NUMBER = 2 * t;
}
static class Node {
boolean leaf = true; //是否是叶子节点
int keyNumber; //有效key
int t; //最小度
int[] keys; // keys数组
Node[] children; //孩子节点数组
public Node(int t) {
this.t = t;
this.keys = new int[2 * t - 1