B树原理及部分方法展示
节点分裂
传入父节点与子节点以及子节点在父节点中的对应位置,若为根节点,则在调用分裂方法前创建新的根节点作为原节点的父节点;新建右子节点,原节点作为左子节点,将左子节点数据中间值放入父节点,右侧值遍历赋给右子节点,若其下存在子节点,则一同过户,删除左子节点中给了父节点与右节点的数据,将右节点挂在父节点下。
//节点存储达到上限,它裂开了
public void splitNode(Node<K,V> root,Node<K,V> child ,int index){
//自我分裂出的新宝宝
Node<K,V> newBaby=new Node<>(this.kComparator);
//原分裂节点若为叶子节点或非叶子节点,新宝宝与之相同
newBaby.setLeaf(child.isLeaf());
//将后半部分养大的孩子扔给新节点 中间值下标为t-1
for(int i=t;i<child.getSize();i++){
newBaby.addEntry(child.getEntrys().get(i));
}
//将原分裂节点的中间元素拿出来准备扔给父母级节点
Entry<K,V> midEntry=child.getEntrys().get(t-1);
//将原分裂节点的中间entry及右边全部删除
for(int i=maxSiez-1;i>=t-1;i--){
child.getEntrys().remove(i);
}
//如果分裂的节点不是叶子节点,则原分裂节点的孩子们要去找新的父母
if( ! child.isLeaf()){
//中间节点右边子节点下标为t,将t及之后的子节点塞给新节点
for(int i=t;i<maxSiez+1;i++){
newBaby.getChildren().add(child.getChildren().get(i));
}
//将塞过的子节点删除,从后向前删否则+1会导致节点漏删
for(int i=maxSiez;i>=t;i--){
child.getChildren().remove(i);
}
}
//将中间元素加入原分裂节点的父节点
addEntry(root,midEntry);
//将分裂出的新节点挂在原分裂节点的父节点,原分裂节点所在下标为index,所以新增节点下标为index+1
root.getChildren().add(index+1,newBaby);
}
查询
使用查询方法,查出数据应该在当前节点的下标。
//二分查找所要插入的entry或所在子节点所在的下标
public Result<V> search(K key){
int begin=0;
int end=this.getSize()-1;
if(end<0){
return new Result<V>(false,null,begin);
}
int mid=(begin+end)/2;
boolean isExist=false;
int index=0;
V value=null;
while(begin<end){
mid=(begin+end)/2;
Entry midEntry=this.entrys.get(mid);
int com=compare((K)midEntry.getKey(),key);
if(com == 0){
break;
}else{
if(com>0){
//key>midEntry.getKey()
begin=mid+1;
}else{
end=mid-1;
}
}
}
//查找结束
if(begin<end){
//在该节点中找到了此entry
isExist=true;
index=mid;
value=this.entrys.get(mid).getValue();
}else{
K midKey=this.entrys.get(begin).getKey();
int com =compare(midKey,key);
if(com == 0){
isExist=true;
index=begin;
value=this.entrys.get(begin).getValue();
}else{
if(com>0){
//key>midkey
isExist=false;
index=begin+1;
value=null;
}else{
isExist=false;
index=begin;
value=null;
}
}
}
return new Result<V>(isExist,value,index);
}
添加
由根节点开始,添加前进行判断,当节点存储个数已经达到最大个数,调用分裂,
分裂后调用查询方法将值插入。
//添加第一步,判断根节点是否已满
public void addNode(Entry<K, V> en) {
if(root.getSize()==maxSiez){
//根节点已满,请先分裂
Node<K,V> newRoot=new Node<>();
newRoot.setLeaf(false);
newRoot.getChildren().add(0,root);
splitNode(newRoot,root,0);
this.root=newRoot;
}
addNodeNotFull(root ,en);
}
//添加第二步,递归找到所应插入的叶子节点
public void addNodeNotFull(Node<K,V> root,Entry<K,V> entry){
if(root.isLeaf()){
//到达叶子节点
addEntry(root,entry);
return;
}
Result<V> result = root.search(entry.getKey());
if(result.isExist()){
return;
}
//不是叶子节点,根据查找出所在子节点下标获取子节点对象
int index=result.getIndex();
Node<K,V> searchChild=root.childAt(index);
//判断子节点是否需要分裂
if(searchChild.getSize()==2*t-1){
splitNode(root,searchChild,index);
//判断插入数据的key与分裂后顶上来的key谁大,决定插入在哪个分裂后的节点
if(root.compare(root.getEntrys().get(index).getKey(),entry.getKey())>0){
//entry Key大用分裂后的新节点,其下标为index+1
searchChild=root.getChildren().get(index+1);
}
}
addNodeNotFull(searchChild,entry);
}
删除
保证父节点数据不为最小个数:
逐级判断若某一节点已是最小个数,则想起左右兄弟节点中不为最小个数的借一位数,左右节点中的数顶替父节点中的数,父节点中的数进入本节点,且带着左右兄弟节点过户来的子节点,若左右兄弟节点都已经是最小个数,则与父节点中的值一起合并为新节点。保证父节点数据数量不为最小个数。
将该值向下旋转:
将其旋转至叶子节点,判断其左右子节点,若存在一个子节点存储数据个数>最小个数,若为子节点,与其最大值交换,右节点与其最小值交换,交换完成后将其从叶子节点中删除;若左右子节点都已经是最小个数,则将该值从父节点移除,与左右子节点合并为一个新节点挂在父节点下,后在叶子节点中删除;
public Entry<K,V> delete(Node<K,V> root,Entry<K,V> entry){
Result<V> result = root.search(entry.getKey());
if(result.isExist()){
//该节点存在所要删除的数
if(root.isLeaf()){
//该节点为叶子节点
return root.removeEntry(result.getIndex());
}
//若不是叶子节点,需要将该数据旋转至子节点直至叶子节点进行删除
Node<K,V> leftChild=root.childAt(result.getIndex());
//若左节点已经是最少数量t-1,则需交给右节点
if(leftChild.getSize() >t-1){
//左节点数量大于t-1,可进行旋转
//删除步骤:将删除节点递归与左节点最后一位或右节点最左一位互换,一直换到叶子节点进行删除
//互换
root.removeEntry(result.getIndex());
root.addEntry(result.getIndex(),leftChild.getEntrys().get(leftChild.getSize()-1));;
leftChild.removeEntry(leftChild.getSize()-1);
leftChild.addEntry(entry);
return delete(leftChild,entry);
}
Node<K,V> rightNode=root.childAt(result.getIndex()+1);
if(rightNode.getSize()>t-1){
//右节点与左节点逻辑相似,只是将最左与删除项进行交换
root.removeEntry(result.getIndex());
root.addEntry(result.getIndex(),rightNode.getEntrys().get(0));;
rightNode.removeEntry(0);
rightNode.addEntry(0,entry);
return delete(rightNode,entry);
}
//如果左右节点数据数量均已是最小值t-1,则将其合并,将删除项与有节点全部放入左节点
leftChild.addEntry(entry);
//左右子节点合并,移除右节点
root.removeChild(result.getIndex()+1);
root.removeEntry(result.getIndex());
//将右节点的数据转移至左节点
for(Entry<K,V> en:rightNode.getEntrys()){
leftChild.addEntry(en);
}
//如果右节点不是存在子节点,则将其子节点也并入左节点
if( ! rightNode.isLeaf()){
for(Node<K,V> node:rightNode.getChildren()){
leftChild.insertChild(node);
}
}
//合并后继续递归删除
return delete(leftChild,entry);
}else{
//删除项不在本节点中,在其子节点中
if(root.isLeaf()){
//如果该节点已经是叶子节点,则表明不在其中
System.out.println("所要删除数据不在本树中");
}
Node<K,V> searchChild=root.childAt(result.getIndex());
//获取所在子节点,判断数量大于t-1则递归删除
if(searchChild.getSize()>t-1){
return delete(searchChild,entry);
}
//所在子节点数量已是最少的t-1,不能直接进入自己点删除,需准备旋转或合并
//先找最亲近的兄弟借钱->兄弟有钱,旋转周转,都没钱一起去抢父母
Node<K,V> brotherNode=null;
//兄弟节点所在下标
int brotherIndex=-1;
if(result.getIndex()<root.getSize()){
//存在右兄弟
Node<K, V> rightBrother = root.childAt(result.getIndex() + 1);
if(rightBrother.getSize()>t-1){
//如果右边兄弟有钱
brotherNode=rightBrother;
brotherIndex=result.getIndex()+1;
}
}
if(brotherNode==null){
//右兄弟没钱或者没有右兄弟,去找左兄弟
if(result.getIndex()>0){
//存在左兄弟
Node<K, V> leftBrother = root.childAt(result.getIndex() - 1);
if(leftBrother.getSize()>t-1){
//左兄弟有钱
brotherNode=leftBrother;
brotherIndex=result.getIndex()-1;
}
}
}
//左右兄弟能借钱
if(brotherNode != null){
if(brotherIndex < result.getIndex()){
//左兄弟借的钱,把父节点中左兄弟下标的数据拿到自己0下标位置,左兄弟最右侧数据塞给父节点
searchChild.addEntry(0,root.getEntrys().get(brotherIndex));
root.removeEntry(brotherIndex);
root.addEntry(brotherIndex,brotherNode.getEntrys().get(brotherNode.getSize()-1));
brotherNode.removeEntry(brotherNode.getSize()-1);
//如果左兄弟有孩子,孩子要过户,左孩子最大的孩子拿来给自己当最小的
if( ! brotherNode.isLeaf()){
searchChild.insertChild(0,brotherNode.childAt(brotherNode.getSize()));
brotherNode.removeChild(brotherNode.getSize()-1);
}
}else{
//右兄弟借的钱,与左兄弟类似,拿右边,放最大
searchChild.addEntry(searchChild.getSize(),root.getEntrys().get(result.getIndex()));
root.removeEntry(result.getIndex());
root.addEntry(result.getIndex(),brotherNode.getEntrys().get(0));
brotherNode.removeEntry(0);
//如果右兄弟有孩子,同样要过户
if( ! brotherNode.isLeaf()){
//右兄弟最小的孩子拿来给自己当最大的
searchChild.insertChild(searchChild.getSize(),brotherNode.childAt(0));
brotherNode.removeChild(0);
}
}
//旋转结束,继续递归删除
return delete(searchChild,entry);
}
//左右兄弟都没钱,把父节点拖一起借钱
if(result.getIndex() < root.getSize()){
//存在右兄弟
Node<K,V> rightBrother=root.childAt(result.getIndex()+1);
//本节点添加与右节点所在父节点的中间数据,父节点移除该数据,父节点移除该数据右子节点
searchChild.addEntry(root.getEntrys().get(result.getIndex()));
root.removeEntry(result.getIndex());
root.removeChild(result.getIndex()+1);
for(Entry<K,V> en:rightBrother.getEntrys()){
searchChild.addEntry(en);
}
if( ! rightBrother.isLeaf()){
//右兄弟不是叶子节点,把它的孩子都抢过来
for(Node<K,V> child:rightBrother.getChildren()){
searchChild.insertChild(child);
}
}
}else{
//没有右兄弟,找左兄弟
Node<K, V> leftBrother = root.childAt(result.getIndex() - 1);
searchChild.addEntry(0,root.getEntrys().get(result.getIndex()-1));
root.removeEntry(result.getIndex()-1);
root.removeChild(result.getIndex()-1);
//
for(int i=leftBrother.getSize()-1;i>=0;i--){
searchChild.addEntry(0,leftBrother.getEntrys().get(i));
}
//如果左节点右孩子,过户
if( ! leftBrother.isLeaf()){
for(int i=leftBrother.getSize();i>=0;i--){
searchChild.insertChild(leftBrother.getChildren().get(i));
}
}
}
if (root == this.root && root.getSize() ==0) {
this.root = searchChild;
}
return delete(searchChild,entry);
}
}
B+树原理与部分方法展示
本文所用B+树
本文展示的B+树为每个节点的最大值提升至父节点中如图
此种方式在添加时添加值大于父节点中A值时需要去A值所对应的子节点中去插入,但是删除时需要去A+1值中去删除,所以增加与删除所对应的查询方法有所区别。
另一种将第二节点开始的最小值提至父节点的B+树应可避免该问题,未作展示请自行尝试;
分裂
与B树分裂相似,只不过将最后一位提至父节点中,且不删除。
//分裂节点
public void splitNode(Node<V> root,Node<V> child,int index){
//先分出一个空白宝宝准备作为大孩子
Node<V> newBoby=new Node<>();
newBoby.setLeaf(child.isLeaf());
newBoby.setParentNode(root);
if(child.isLeaf()){
//如果是叶子节点,设置叶子链表
newBoby.setNextNode(child.getNextNode());
newBoby.setPreviousNode(child);
child.setNextNode(newBoby);
}
//将原分裂节点后半部分孩子全部扔给新节点
for(int i=minSize;i<maxSize;i++){
newBoby.addKey(child.keyAt(i));
if(child.isLeaf()){
newBoby.addEntry(child.entryAt(i));
}
}
//移除原分裂节点中后半部分的元素
for(int i=maxSize-1;i>=minSize;i--){
child.deleteKey(i);
if(child.isLeaf()){
child.deleteEntry(i);
}
}
//将分裂后的俩个最大值拿出来准备更新及塞入父节点
Integer newKey1=child.keyAt(child.getKeysSize()-1);
Integer newKey2=newBoby.keyAt(newBoby.getKeysSize()-1);
//如果分裂的节点不是叶子节点,它的孩子也要过户
if( ! child.isLeaf()){
for(int i=minSize;i<maxSize;i++){
newBoby.addChild(child.childAt(i));
child.childAt(i).setParentNode(newBoby);
}
//将过户的孩子扔掉
for(int i=maxSize-1;i>=minSize;i--){
child.deleteChild(i);
}
}
//更新父节点中的值及子节点
root.updateKey(index,newKey1);
root.addKey(newKey2,index+1);
root.addChild(newBoby,index+1);
}
查询
查询需分为俩种情况,一种为添加时一种为删除时例如跟节点的key值为3,6,9
添加5时,需先去3所对应的子节点中去添加
删除5时需去6对应的子节点中去删除
添加查询
//二分查找所查找key应该在节点哪
public Result<V> search(Integer key ){
int begin=0;int end=this.getKeysSize()-1;
int mid;
int index=-1;
Entry<V> entry=null;
//node为空直接返回
if(this.getKeysSize() <=0){
return new Result<>(null,entry,index,false);
}
//若节点为叶子节点且不存在
// if(this.isLeaf() && ! this.getKeys().contains(key)){
// return new Result<>(null,entry,index,false);
// }
while(begin<end){
mid=(begin+end)/2;
int com=this.keyAt(mid).compareTo(key);
if(com==0){
//中间值为所查找值
index=mid;
if(this.isLeaf()){
entry=this.entryAt(mid);
}
return new Result<>(key,entry,index,true);
}else if(com <0){
//key大
begin=mid+1;
}else{
//mid大
end=mid-1;
}
}
int com = this.keyAt(begin).compareTo(key);
if(com == 0){
index=begin;
if(this.isLeaf()){
entry=this.entryAt(begin);
}
return new Result<>(key,entry,index,true);
}else if(com < 0){
//begin所在位置key小与查询key
index=begin;
if(this.isLeaf()){
//如果不是叶子节点,子节点下边等同begin,如果是叶子节点,插入需要+1,因为这种B+树子节点少一个头
index=begin+1;
}
return new Result<>(key,null,index,false);
}else{
//begin所在位置key大于查询key
index=begin;
return new Result<>(key,null,index,false);
}
}
删除查询
//二分查找所查找key应该在节点哪
public Result<V> deleteSearch(Integer key ){
int begin=0;int end=this.getKeysSize()-1;
int mid;
int index=-1;
Entry<V> entry=null;
//node为空直接返回
if(this.getKeysSize() <=0){
return new Result<>(null,entry,index,false);
}
//若节点为叶子节点且不存在
// if(this.isLeaf() && ! this.getKeys().contains(key)){
// return new Result<>(null,entry,index,false);
// }
while(begin<end){
mid=(begin+end)/2;
int com=this.keyAt(mid).compareTo(key);
if(com==0){
//中间值为所查找值
index=mid;
if(this.isLeaf()){
entry=this.entryAt(mid);
}
return new Result<>(key,entry,index,true);
}else if(com <0){
//key大
begin=mid+1;
}else{
//mid大
end=mid-1;
}
}
int com = this.keyAt(begin).compareTo(key);
if(com == 0){
index=begin;
if(this.isLeaf()){
entry=this.entryAt(begin);
}
return new Result<>(key,entry,index,true);
}else if(com < 0){
//begin所在位置key小与查询key
index=begin + 1;
return new Result<>(key,null,index,false);
}else{
//begin所在位置key大于查询key
index=begin;
return new Result<>(key,null,index,false);
}
}
添加
添加时判断第一次添加需设置头节点,其次判断其是否已经达到最大存储,满则分裂,不满则循环至叶子节点插入,若其为最大值,则需循环向上更新key值。
循环更新最大值
/**
* 循环更新父节点,传入更新的叶节点
*/
private void doWhileUpdateBigestkey(Node<V> entry,Integer key ){
while(entry != null && entry.getParentNode() != null) {
Result<V> parentResult = entry.getParentNode().search(key);
if(entry.getParentNode().keyAt(entry.getParentNode().getKeysSize() - 1) < key) {
//如果也是父节点的最大值,继续向上变
entry.getParentNode().updateKey(parentResult.getIndex(), key);
entry=entry.getParentNode();
}else{
//不是父节点的最大key,只更换对应值
entry.getParentNode().updateKey(parentResult.getIndex(),key);
entry=null;
}
}
}
删除
循环至叶子节点删除,若其为最大值,需向上更新最大值,合并节点与兄弟节点合并采用将所有值及节点全部挂至右侧兄弟,若头节点被移除需重新指定头节点,其余像兄弟借值与B树类似
/**
* 合并节点
*/
private void mergeNode(Node<V> root, Node<V> node, Integer index){
//需要借钱或合并--看是否存在左右兄弟
Node<V> brotherNode = null;
Integer brotherIndex=null;
if(index < root.getKeysSize()-1){
//存在右兄弟 准备开口
Node<V> rightNode = root.childAt(index + 1);
if(rightNode.getKeysSize() > minSize){
//右兄弟有钱,准备抢
brotherNode=rightNode;
brotherIndex=index+1;
}
}
if(brotherNode == null){
//没有右兄弟,去看看有没有弟弟
if(index > 0){
//有弟弟 开口
Node<V> leftNode = root.childAt(index - 1);
if(leftNode .getKeysSize() > minSize){
//抢
brotherNode=leftNode;
brotherIndex=index-1;
}
}
}
if(brotherNode == null){
//兄弟都没钱,找父母
if(index < root.getKeysSize() - 1){
//和右兄弟合体
Node<V> rightNode = root.childAt(index + 1);
root.deleteChild(index + 1);
root.deleteKey(index);
for (int i = 0; i < rightNode.getKeysSize(); i++){
node.addKey(rightNode.keyAt(i));
if(node.isLeaf()){
node.addEntry((rightNode.entryAt(i)));
node.setNextNode(null);
}else{
node.addChild(rightNode.getChildren().get(i));
}
}
if(this.root.getKeysSize() == 1){
this.root = node;
}
}else{
//和左兄弟合体
Node<V> leftNode = root.childAt(index - 1);
root.deleteChild(index - 1);
root.deleteKey(index - 1);
if(head == leftNode){
head = node;
}
for (int i = leftNode.getKeysSize() - 1; i >= 0; i--){
node.addKey(leftNode.keyAt(i));
if(node.isLeaf()){
node.addEntry(node.getEntrys().get(i));
node.setPreviousNode(leftNode.getPreviousNode());
leftNode.getPreviousNode().setNextNode(node);
}else{
node.addChild(leftNode.getChildren().get(i));
}
}
}
}else {
//抢兄弟
if(brotherIndex > index){
//右兄弟
Integer brotherKey = brotherNode.keyAt(0);
brotherNode.deleteKey(0);
node.addKey(brotherKey);
if(node.isLeaf()){
//叶子节点
node.addEntry(brotherNode.entryAt(0));
brotherNode.deleteEntry(0);
}else{
//非叶子节点
node.addChild(brotherNode.childAt(0));
brotherNode.deleteChild(0);
}
//循环更新最大值
doWhileUpdateBigestkey(root,brotherKey);
}else {
//左兄弟
Integer brotherKey = brotherNode.keyAt(brotherNode.getKeysSize() - 1);
brotherNode.deleteKey(brotherNode.getKeysSize() - 1);
node.addKey(brotherKey,0);
if(node.isLeaf()){
//叶子节点
node.addEntry(brotherNode.entryAt(brotherNode.getChildrenSize() - 1));
brotherNode.deleteEntry(brotherNode.getChildrenSize() - 1);
}else {
//非叶子节点
node.addChild(brotherNode.childAt(brotherNode.getChildrenSize() - 1), 0);
brotherNode.deleteChild(brotherNode.getKeysSize() - 1);
}
root.updateKey(index - 1, brotherKey);
}
}
}
源码地址
https://github.com/FifthMemo/BTreeAndBTreePlus.git