1.出现缘由
当数据量很大时,内存不够,找磁盘。为了提高取数据的速度,减少树的高度->多叉
2.特点
- 每个结点包含多个关键字,包含上界和下界。最小度数(取决于磁盘块)做衡量t>=2
- 所有的叶子结点都在同一层,并且不带信息 (可以看做是外部结点或查找失败的结 点,实际上这些结点不存在,指向这些结点的指针为空)
- 对于一个有x个关键字的结点,它有x+1个孩子
- 每个结点中的关键字升序
- 根结点至少包含一个关键字,但是上界是一样的
3.插入
主动插入,往叶子结点插
在插入寻找的过程中,只要碰到满结点都做拆分,不管他是不是目标结点
4.删除
4.1如果待删除的结点在结点x中 且x是叶子结点直接删除
4.2如果待删除的结点在结点x中 且x是内部结点。
4.2.1如果位于x中某个关键字前一个孩子y至少有t个关键字
删除关键字G,其前一个孩子为DEF,关键字G的直接前驱为F,删除F,然后将G替换为F
4.2.2如果位于x中某个关键字前一个孩子y少于t个关键字,后一个孩子不少于t个关键字
删除关键字C,其前一个孩子为AB,小于t=3,后一个孩子DEF不小于3,关键字C的直接后继为D,删除D,然后将C替换为D
4.2.3如果位于x中某个关键字前、后孩子都少于t个关键字
删除关键字C,前一个孩子为AB,后一个孩子为DE(此处假设F已经被删掉了),均小于t=3,合并关键字C和结点DE到结点AB中,此时C在叶子结点中,直接删除
4.3如果打破了b树规则(删除是递归过程,删除和调整是一体的)
4.3.1如果子树和他相邻的兄弟都只有t-1,子树和兄弟合并,父结点一个关键字下移
4.3.2如果只有子树是t-1相邻的兄弟至少t个,则将x的一个关键字下移到子树中,将相邻的兄弟中的一个关键字上移到x中,相应的孩子指针也上移,使得x新增加一个关键字(直接返回不需要递归)
package Tree.bTree;
import java.util.LinkedList;
public class BTreeNode {
//b树的阶
int M;
//关键字列表
LinkedList<Integer> values;
//父结点
BTreeNode parent;
//孩子列表
LinkedList<BTreeNode> children;
//构造构造一棵空的B-树
private BTreeNode(){
this.values = new LinkedList<Integer>();
this.children = new LinkedList<BTreeNode>();
}
//构造一棵空的m阶B-树
public BTreeNode(int m){
this();
if(m<3){
throw new RuntimeException("阶数大于2");
}
this.M = m;
}
//根据父结点构造一个空的孩子结点
public BTreeNode(BTreeNode parent){
this(parent.M);
this.parent = parent;
}
/**
* 获取根结点
* @return 根结点
*/
public BTreeNode getRoot(){
BTreeNode p = this;
while(!p.isRoot()){
p = p.parent;
}
return p;
}
/**
* 判断当前结点是不是根结点
* @return 是就返回真
*/
public boolean isRoot(){
return parent == null;
}
/**
* 判断当前结点是不是空结点
* @return 是就真
*/
public boolean isEmpty(){
if(values == null||values.size()==0){
return true;
}
return false;
}
/**
* 清空当前结点,保留父关系
*/
public void clear(){
values.clear();
children.clear();
}
/**
* 从当前结点往下查找目标值target
* @param target 目标值
* @return 找到返回找到的结点,没有就返回null的叶子结点
*/
public BTreeNode search(int target){
if(isEmpty()){
return this;
}
int valueIndex = 0;
while(valueIndex < values.size()&&values.get(valueIndex)<=target){
if(values.get(valueIndex) == target){
return this;
}
valueIndex++;
}
return children.get(valueIndex).search(target);
}
/**
*插入结点,先找到根结点,从根结点往下找到插入的位置,如果是重复的数据就抛出异常
* 插入可能会产生新的根结点,返回新的结点
* @param e 插入的关键字
* @return 新的根结点
*/
public BTreeNode insert(int e){
if(isEmpty()){
values.add(e);
children.add(new BTreeNode(this));
children.add(new BTreeNode(this));
return this;
}
BTreeNode p = getRoot().search(e);
if(!p.isEmpty()){
throw new RuntimeException("cannot insert duplicate key to B-Tree, key: " + e);
}
insertNode(p.parent,e,new BTreeNode(p.M));
return getRoot();
}
/**
* 向指定结点内插入关键字和关键字右侧的孩子结点,如果没有大于2m-1,就找到结点内部的插入位置,然后直接插入
* 一定是叶子插入
* @param node 插入的结点
* @param e 待插入的关键字
* @param eNode 待插入的关键字右侧的孩子结点
*/
private void insertNode(BTreeNode node,int e,BTreeNode eNode){
int valueIndex = 0;
while(valueIndex < node.values.size()&&node.values.get(valueIndex)<e){
valueIndex++;
}
node.values.add(valueIndex,e);
eNode.parent = node;
node.children.add(valueIndex+1,eNode);
//这里的M阶相当于2m-1
if(node.values.size()>M-1){
int upIndex = M/2;
int up = node.values.get(upIndex);
//当前结点分为左右两部分,左边的parent不变,新建右边结点瓜分原来的右半边
BTreeNode rNode = new BTreeNode(M);
rNode.values = new LinkedList<>(node.values.subList(upIndex+1,M));
rNode.children = new LinkedList<>(node.children.subList(upIndex+1,M+1));
for (BTreeNode rChild : rNode.children){
rChild.parent = rNode;
}
node.values = new LinkedList<>(node.values.subList(0,upIndex));
node.children = new LinkedList<>(node.children.subList(0,upIndex+1));
//从根结点上升,选取上升关键字作为新的根结点
if(node.parent == null){
node.parent = new BTreeNode(M);
node.parent.values.add(up);
node.parent.children.add(node);
node.parent.children.add(rNode);
rNode.parent = node.parent;
return;
}
//父结点增加关键字,递归调用
insertNode(node.parent,up,rNode);
}
}
/**
* 以当前节点为根,在控制台打印B-树
*/
public void print() {
printNode(this, 0);
}
/**
* 控制台打印节点的递归调用
*
* @param node 要打印节点
* @param depth 递归深度
*/
private void printNode(BTreeNode node, int depth) {
StringBuilder sb = new StringBuilder();
for(int i = 1; i < depth; i++) {
sb.append("| ");
}
if(depth > 0) {
sb.append("|----");
}
sb.append(node.values);
System.out.println(sb.toString());
for(BTreeNode child : node.children) {
printNode(child, depth+1);
}
}
public BTreeNode delete(int e){
if(isEmpty()){
return this;
}
BTreeNode p = getRoot().search(e);
if(p.isEmpty()) {
throw new RuntimeException("the key to be deleted is not exist, key: " + e);
}
//找到目标结点的位置
int valueIndex = 0;
while(valueIndex < p.values.size() && p.values.get(valueIndex) < e){
valueIndex++;
}
//不是最下层结点中的关键字
if(!p.children.get(0).isEmpty()){
BTreeNode rMin = p.children.get(valueIndex);
while(!rMin.children.get(0).isEmpty()){
//一直找到叶子结点
rMin = rMin.children.get(0);
}
//用最小的最大关键字替换
p.values.set(valueIndex,rMin.values.get(0));
//删除了最下层结点有可能导致关键字数小于t-1
//rMin是最下层结点,valueIndex是原结点的位置
return delete(rMin,valueIndex,0);
}
return delete(p,valueIndex,0);
}
/**
* 删除指定结点中的关键字和孩子,并处理删除后打破b-树规则的情况
* @param target 目标结点
* @param valueIndex 关键字索引
* @param childIndex 孩子索引
* @return 删除完成后的根结点
*/
private BTreeNode delete(BTreeNode target,int valueIndex,int childIndex){
target.values.remove(valueIndex);
target.children.remove(childIndex);
//目标结点满足规则直接返回,不进行下一步调整
if(target.children.size() >= Math.ceil(M/2.0)){
return target.getRoot();
}
//根结点比较特殊
if(target.isRoot()){
//孩子数量满足要求
if(target.children.size()>1){
return target;
} else{
//只有一个孩子,直接上移,返回
BTreeNode newRoot = target.children.get(0);
newRoot.parent = null;
return newRoot;
}
}
//找到目标结点的位置
int parentChildIndex = 0;
while(parentChildIndex < target.parent.children.size() && target.parent.children.get(parentChildIndex) != target) {
parentChildIndex++;
}
/**
* 等于0表示没有左兄弟
* 不满足b树结构要求,下面就是进行调整步骤
*/
if(parentChildIndex > 0 &&target.parent.children.get(parentChildIndex - 1).values.size() >= Math.ceil(M/2.0)){
//左兄弟关键字数大于t-1
int downKey = target.parent.values.get(parentChildIndex-1);
BTreeNode leftSibling = target.parent.children.get(parentChildIndex-1);
int upKey = leftSibling.values.remove(leftSibling.values.size()-1);
//删除孩子用来替换的关键字
BTreeNode mergeChild = leftSibling.children.remove(leftSibling.children.size()-1);
//关键字下移
target.values.add(0, downKey);
//孩子指针也上移
target.children.add(0, mergeChild);
//替换
target.parent.values.set(parentChildIndex-1, upKey);
return target.getRoot();
} else if(parentChildIndex < target.parent.children.size()-1 &&
target.parent.children.get(parentChildIndex+1).values.size() >= Math.ceil(M/2.0)){
// 右兄弟关键字数大于 ceil(M/2)-1,且有右孩子
int downKey = target.parent.values.get(parentChildIndex);
BTreeNode rightSibling = target.parent.children.get(parentChildIndex+1);
int upKey = rightSibling.values.remove(0);
BTreeNode mergeChild = rightSibling.children.remove(0);
target.values.add(downKey);
target.children.add(mergeChild);
target.parent.values.set(parentChildIndex, upKey);
return target.getRoot();
} else{
//左右兄弟关键字数都不大于t-1
int parentValueIndex;
if(parentChildIndex > 0){
//如果有左兄弟,和左兄弟合并
BTreeNode leftSibling = target.parent.children.get(parentChildIndex -1);
//加上父结点的关键字
parentValueIndex = parentChildIndex - 1;
int downKey = target.parent.values.get(parentValueIndex);
leftSibling.values.add(downKey);
//加上目标结点的剩余信息
leftSibling.values.addAll(target.values);
target.children.forEach(c -> c.parent = leftSibling);
leftSibling.children.addAll(target.children);
} else{
//没有左兄弟,和右兄弟合并
BTreeNode rightSibling = target.parent.children.get(parentChildIndex + 1);
//加上父结点关键字
parentValueIndex = parentChildIndex;
int downKey = target.parent.values.get(parentValueIndex);
rightSibling.values.add(0,downKey);
//加上目标结点的剩余信息
rightSibling.values.addAll(0, target.values);
target.children.forEach(c -> c.parent = rightSibling);
rightSibling.children.addAll(0,target.children);
}
//递归删除父结点关键字和孩子
return delete(target.parent,parentValueIndex,parentChildIndex);
}
}
}