七、B-树
种类 -最后随机 O( ) 哈希 无要求 O(1) | 数据格式 | 时间复杂度 |
---|---|---|
顺序查找 | 无要求 | O(N) |
二分查找 | 有序 | O(log2N) |
二叉搜索树 | 无要求 | O(log2N) 或者 O(N) |
二叉平衡树(AVL树和红黑树) | 无要求- 最后随机 | O(log2N) |
哈希 | 无要求 | O(1) |
位图 | 无要求 | O(1) |
布隆过滤器 | 无要求 | O(K)(K为哈希函数个数,一般比较小) |
以上结构适合用于数据量不是很大的情况,如果数据量非常大,一次性无法加载到内存中,使用上述结构就不是很方便。比如:使用平衡树搜索一个大文件。
上面方法其实只在内存中保存了每一项数据信息中需要查找的字段以及数据在磁盘中的位置,整体的数据实际也在磁盘中。缺陷:
- 树的高度比较高,查找时最差情况下要比较树的高度次
- 数据量如果特别大时,树中的节点可能无法一次性加载到内存中,需要多次IO
那如何加速对数据的访问呢? - 提高IO的速度
- 降低树的高度—多叉树平衡树
0、B-树
1970年,R.Bayer和E.mccreight提出了一种适合外查找的树,它是一种平衡的多叉树,称为B树(有些地方写的是B树,注意不要误读成"B减树")。一棵M阶(M>2)的B树,是一棵平衡的M路平衡搜索树,可以是空树或者满足一下性质:
- 根节点至少有两个孩子。
- 每个非根节点至少有M/2-1(上取整)个关键字,至多有M-1个关键字,并且以升序排列。
例如:当M=3的时候,至少有3/2=1.5,向上取整等于2,2-1=1个关键字,最多是2个关键字。 - 每个非根节点至少有M/2(上取整)个孩子,至多有M个孩子。
例如:当M=3的时候,至少有3/2=1.5,向上取整等于2个孩子。最多有3个孩子。 - key[i]和key[i+1]之间的孩子节点的值介于key[i]、key[i+1]之间。
- 所有的叶子节点都在同一层。
为了简单起见,假设M = 3. 即三叉树,每个节点中存储两个数据,两个数据可以将区间分割成三个部分,因此节点 应该有三个孩子,为了后续实现简单期间,节点的结构如下:
注意:孩子永远比数据多一个。
插入过程当中,有可能需要分裂,分裂的 前提是: 假设,当前是要组成一个M路查找树,关键字数必须小于等于M-1(这里关键字数>M-1就要进行节点拆分) 规则是: 把中间的元素,提取出来,放到父亲节点上,左边的单独构成一个节点,右边的单独构成一个节点。 用序列{53, 139, 75, 49, 145, 36, 101}构建B树的过程如下:
插入过程总结:
- 如果树为空,直接插入新节点中,该节点为树的根节点
- 树非空,找待插入元素在树中的插入位置(注意:找到的插入节点位置一定在叶子节点中)
- 检测是否找到插入位置(假设树中的key唯一,即该元素已经存在时则不插入)
比特就业课 - 按照插入排序的思想将该元素插入到找到的节点中
- 检测该节点是否满足B-树的性质:即该节点中的元素个数是否等于M,如果小于则满足
- 如果插入后节点不满足B树的性质,需要对该节点进行分裂:
申请新节点
找到该节点的中间位置
将该节点中间位置右侧的元素以及其孩子搬移到新节点中
将中间位置元素以及新节点往该节点的双亲节点中插入,即继续4 - 如果向上已经分裂到根节点的位置,插入结束
1、B-树插入实现
public class Pair <K,V>{
private K key;
private V val;
public Pair(K key, V val) {
this.key = key;
this.val = val;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getVal() {
return val;
}
public void setVal(V val) {
this.val = val;
}
}
public class MyBtree {
static class BTRNode {
public int[] keys;//关键字
public BTRNode[] subs;//孩子
public BTRNode parent;//存储当前孩子节点的父亲节点
public int usedSize;//记录当前节点中关键字的数量
public BTRNode () {
//说明一下:这里多给一个 是为了好进行分裂
this.keys = new int[M];
this.subs = new BTRNode[M+1];
}
}
public static final int M = 3;
public BTRNode root;//当前B树的根节点
/**
* 往B树当中 插入一个元素
* @param key
*/
public boolean insert(int key) {
//1、如果B树为空的时候
if(root == null) {
root = new BTRNode();
root.keys[0] = key;
root.usedSize++;
return true;
}
//2、当B树不为空的时候,我们需要查看当前B树当中 是否存在我的Key
Pair<BTRNode,Integer> pair = find(key);
//判断 这里获取到的val值 是不是-1 来确定 当前是否存在该key
if(pair.getVal() != -1) {
return false;
}
//3、说明不存在这个key 我们要进行插入
BTRNode parent = pair.getKey();
int index = parent.usedSize-1;
for (; index >= 0;index--) {
if(parent.keys[index] >= key) {
parent.keys[index+1] = parent.keys[index];
}else {
break;
}
}
parent.keys[index+1] = key;
parent.usedSize++;
//为什么不处理 孩子呢 因为你每次插入都是再叶子节点,所以叶子节点都是null
if(parent.usedSize < M) {
//没有满
return true;
}else {
//满了-》分裂
split(parent);
return true;
}
}
/**
* 分裂的逻辑
* @param cur 当前需要分裂的节点
*/
private void split(BTRNode cur) {
BTRNode newNode = new BTRNode();
//1. 先存储当前需要分裂节点的父节点
BTRNode parent = cur.parent;
//2. 开始挪数据
int mid = cur.usedSize >> 1;
int i = mid+1;
int j = 0;
for( ; i < cur.usedSize;i++) {
newNode.keys[j] = cur.keys[i];
newNode.subs[j] = cur.subs[i];
//处理刚刚拷贝过来的孩子节点的父亲节点 为新分裂的节点
if(newNode.subs[j]!=null) {
newNode.subs[j].parent = newNode;
}
j++;
}
//多拷贝一次孩子
newNode.subs[j] = cur.subs[i];
if(newNode.subs[j]!=null) {
newNode.subs[j].parent = newNode;
}
//更新当前新节点的有效数据
newNode.usedSize = j;
//这里的-1 指的是 将来要提到父亲节点的key
cur.usedSize = cur.usedSize - j - 1;
//特殊:处理根节点的情况
if(cur == root) {
root = new BTRNode();
root.keys[0] = cur.keys[mid];
root.subs[0] = cur;
root.subs[1] = newNode;
root.usedSize = 1;
cur.parent = root;
newNode.parent = root;
return;
}
//更新当前新的节点的父亲节点
newNode.parent = parent;
//开始移动父亲节点
int endT = parent.usedSize-1;
int midVal = cur.keys[mid];
for (; endT >= 0 ; endT--) {
if(parent.keys[endT] >= midVal) {
parent.keys[endT+1] = parent.keys[endT];
parent.subs[endT+2] = parent.subs[endT+1];
}else {
break;
}
}
parent.keys[endT+1] = midVal;
//将当前父节点的孩子节点 新增为newNode
parent.subs[endT+2] = newNode;
parent.usedSize++;
if(parent.usedSize >= M) {
split(parent);
}
}
private Pair<BTRNode,Integer> find(int key) {
BTRNode cur = root;
BTRNode parent = null;
while (cur != null) {
int i = 0;
while (i < cur.usedSize) {
if(cur.keys[i] == key) {
//返回一个当前找到的节点 和 当前这个数据在节点当中的下标
return new Pair<>(cur,i);
}else if(cur.keys[i] < key) {
i++;
}else {
break;
}
}
parent = cur;
cur = cur.subs[i];
}
return new Pair<>(parent,-1);
}
public static void main(String[] args) {
MyBtree myBtree = new MyBtree();
int[] array = {53, 139, 75, 49, 145, 36,101};
for (int i = 0; i < array.length; i++) {
myBtree.insert(array[i]);
}
System.out.println("fdsafafa");
myBtree.inorder(myBtree.root);
}
private void inorder(BTRNode root){
if(root == null) {
return;
}
for(int i = 0; i < root.usedSize; ++i){
inorder(root.subs[i]);
System.out.println(root.keys[i]);
}
inorder(root.subs[root.usedSize]);
}
}
3、B-性能分析
4、B+树
5、B*树