java使用btree_B树的 JAVA 实现

因为感觉对 B 树的理解不是特别深刻,一直想手撸一个 B 树,这次终于得偿所愿,文末有完整的 B 树代码。

代码比较长,大概六百行。

B 树的代码使用了一百组数据进行 插入/删除 测试,结果正确。

从生产讲,实现一棵 B 树不会有什么实际意义,但是这些代码和构建这些代码的思路,都将成为我们职业素养的一部分。

什么是B树

在1970年,Bayer&McCreight发表的论文《ORGANIZATION AND MAINTENANCE OF LARGE ORDERED INDICES 》(大型有序索引的组织和维护)中提出了一种新的数据结构来维护大型索引,这种数据结构在论文中称为B-Tree。

B 树是一种多路查找树,相比于二叉树来说,B 树更适合于建立存储设备中的文件索引。

因为对于存储设备的操作,除算法的时间复杂度外,查找一个数据所需要进行 I/O 操作的次数也是性能的重要影响因素。

对于传统的二叉树来说,其存储比较松散,树的深度较深,我们每次查找一个新节点,可能都需要进行一次 I/O 操作将其读入内存。

而 B 树因为数据存储比较集中,一个节点内存储的数据更多,树的深度较浅,遍历整个 B 树所需 I/O 操作的次数远小于二叉树,同时因为我们的存储设备普遍适配了局部性原理,对于一个连续存储的节点来说,完全读取它所需 I/O 操作的次数也是非常少的。

从算法时间复杂度上看,因为 B 树节点中的项都是有序存储的,我们在一个节点内寻找数据时,使用二分查找可以使时间复杂度落在 O(log2N) 内,与在二叉树中的查找相同。因此总的来看,B 树相比于二叉树更适用于在存储设备上维护文件索引。

另外,B 树是平衡的,其维护平衡性的思路非常典型。对于普通的二叉树,是自上向下生长的,而对于B树,是自下而上生长的。

我们的插入操作都只会将新项插入到叶子节点,叶子节点满后进行分裂,并将中间节点向上融合。整颗树高度的增加必然是由根节点的分裂带来的,而根节点的分裂不会破坏整颗树的平衡性,因为左右子树的高度均加一。

对于一棵完全的二叉排序树,其根节点必然是整个有序链表的中点。这对于 B 树也是相通的,每次的分裂和向上融合都是向上传递了本节点的中点,所有元素组成的链表中,中间部分会集中在 B 树的上层节点中,这也保证了整棵树的平衡性。

我们平时使用的红黑树就是 2-3树(B树的一种)的一个变种,通过为二叉树节点染色,模仿 2-3 树的节点合并/分裂等行为,为其在平衡性和性能上找到了一个妥协点。

B树的定义

h:代表树的高度,k 是个自然数,一个B树要么是空的,要么满足以下条件:

1.所有叶子节点到根节点的路径长度相同,即具有相同的高度;(树是平衡的)

2.每个非叶子和根节点(即内部节点)至少有 k+1 个孩子节点,根至少有 2 个孩子;(这是关键的部分,因为节点都是分裂而来的,而每次分裂得到的节点至少有 k 个元素,也就有 k+1 个孩子;但根节点在分裂后可能只有一个元素,因为不需要向上融合,中间元素作为新的根节点,因此最少有两个孩子。而叶子节点没有孩子。)

3.每个节点最多有 2k+1 个孩子节点。(规定了节点的最大容量)

4.每个节点内的键都是递增的

我们定义一个树的结构,首先定义其数据项及节点的数据结构:

/*** @Author Nxy

* @Date 2020/2/23 14:06

* @Description 内部类,B树中节点中的元素。K:键类型,V:值类型,可以是指向数据的索引,也可以是实体数据*/

private class Entry{privateK key;privateV value;public voidsetKey(K key) {this.key =key;

}publicK getKey() {return this.key;

}public voidsetValue(V value) {this.value =value;

}publicV getValue() {return this.value;

}

@OverridepublicString toString() {return "key: " + this.key + " , ";

}

}/*** @Author Nxy

* @Date 2020/2/23 14:12

* @Description 内部类,封装搜索结果*/

private class SearchResult{private booleanisExist;privateV value;private intindex;//构造方法,将查询结果封装入对象

public SearchResult(boolean isExist, intindex, V value) {this.isExist =isExist;this.index =index;this.value =value;

}public booleanisExist() {returnisExist;

}publicV getValue() {returnvalue;

}public intgetIndex() {returnindex;

}

}/*** @Author Nxy

* @Date 2020/2/23 14:28

* @Description 树的节点*/

public class Node{//节点内的项

private List>entrys;//节点的孩子节点们

private List>sons;//是否是叶子节点

private booleanisLeaf;//键值比较函数对象,如果采用倒序或者其它排序方式,传入该对象

private ComparatorkComparator;//比较两个key,如果没有传入自定义排序方式则采用默认的升序

private intcompare(K key1, K key2) {return this.kComparator == null ? ((Comparable) key2).compareTo(key1) : kComparator.compare(key1, key2);

}//普通构造函数

Node() {this.entrys = new LinkedList>();this.sons = new LinkedList>();this.isLeaf = false;

}//自定义K排序方式的构造函数

Node(ComparatorkComparator) {this();this.kComparator =kComparator;

}public void setIsLeaf(booleanisLeaf) {this.isLeaf =isLeaf;

}public booleangetIsLeaf() {return this.isLeaf;

}//返回本节点的项数

public intnodeSize() {return this.entrys.size();

}/*** @Author Nxy

* @Date 2020/2/23 15:19

* @Param key:待查找元素的key值

* @Return 查找结果封装入 SearchResult

* @Exception

* @Description 在本节点内查找元素, 本质就是一个有序数组的二分查找*/

public SearchResultsearch(K key) {int begin = 0;int end = this.nodeSize() - 1;//if (end == 0) {//return new SearchResult(false, 0, null);//}

int mid = (begin + end) / 2;boolean isExist = false;int index = 0;

V value= null;//二分查找

while (begin

mid= (begin + end) / 2;

Entry midEntry= this.entrys.get(mid);int compareRe =compare((K) midEntry.getKey(), key);//找到了

if (compareRe == 0) {break;

}else{if (compareRe > 0) {//在中点右边

begin = mid + 1;

}else{

end= mid - 1;

}

}

}//二分查找结束,判断结果;三个元素以上才是正经二分,只有两个或一个元素属于边界条件要着重考虑

if (begin

isExist = true;

index=mid;

value= this.entrys.get(mid).getValue();

}else if (begin ==end) {

K midKey= this.entrys.get(begin).getKey();int comRe =compare(midKey, key);if (comRe == 0) {

isExist= true;

index=begin;

value= this.entrys.get(mid).getValue();

}else if (comRe > 0) {

isExist= false;

index= begin + 1;

value= null;

}else{

isExist= false;

index=begin;

value= null;

}

}else{

isExist= false;

index=begin;

value= null;

}return new SearchResult(isExist, index, value);

}//删除给定索引位置的项

public Entry removeEntry(intindex) {

Entry re = this.entrys.get(index);this.entrys.remove(index);returnre;

}//得到index处的项

public Entry entryAt(intindex) {return this.entrys.get(index);

}//将新项插入指定位置

private void insertEntry(Entry entry, intindex) {this.entrys.add(index, entry);

}//节点内插入项

private boolean insertEntry(Entryentry) {

SearchResult result =search(entry.getKey());if(result.isExist()) {return false;

}else{

insertEntry(entry, result.getIndex());return true;

}

}//更新项,如果项存在,更新其值并返回原值,否则直接插入

public V putEntry(Entryentry) {

SearchResult re =search(entry.getKey());if(re.isExist) {

Entry oldEntry= this.entrys.get(re.getIndex());

V oldValue=(V) oldEntry.getValue();

oldEntry.setValue(entry.getValue());returnoldValue;

}else{

insertEntry(entry);return null;

}

}//获得指定索引的子节点

public Node childAt(intindex) {return this.sons.get(index);

}//删除给定索引的子节点

public void removeChild(intindex) {this.sons.remove(index);

}//将新的子节点插入到指定位置

public void insertChild(Node child, intindex) {this.sons.add(index, child);

}

}

然后定义树的数据结构:

public classBtree{//度数T,不传入则默认为 2-3 树

private Integer DEFAULT_T = 2;//根节点

private Noderoot;private int t =DEFAULT_T;//非根节点的最小项数,体现的是除了根节点,其余节点都是分裂而来的!

private int nodeMinSize = t - 1;//节点的最大项数

private int nodeMaxSize = 2 * t - 1;//比较函数对象

private ComparatorkComparator;

}

B树的操作

对于一棵树常用的操作无非是增删改查,其余诸如旋转之类的调整树结构的操作封装在增删改查内部。我们看一下 B 树增删改查的逻辑。

B树的查找

查找是 B 树最简单的操作:

1.在节点内进行二分查找,如果找到则返回。未找到返回其在节点内插入时的索引。

2.根据索引递归的查找子节点,直到查找到叶子节点仍未找到则表明数据不在树中。

实现如下:

//在以root为根的树内搜索key项

private V search(Noderoot, K key) {

SearchResult re =root.search(key);if(re.isExist) {returnre.value;

}else{//回归条件

if(root.isLeaf) {return null;

}int index =re.index;//递归搜索子节点

return(V) search(root.childAt(index), key);

}

}publicV search(K key) {return search(this.root, key);

}

B树的插入

然后是B树的插入操作,插入操作需要考虑节点的分裂与向上融合,整个的过程如下:

1. 首先判断根节点是否已满,满则先进行分裂得到新的根节点。

2.从根节点向下寻找,找到待插入数据在叶子节点的位置。

3. 向下寻找时,每经过一个节点,都检查插入位置子节点的大小是否为 2*t-1 ,因为我们插入节点可能造成下层节点的向上融合,上层节点如果大小为 2*t-1 ,融合新元素后将会有数据超过最大值的危险。

寻找路径上,只要有节点的大小为 2*t-1 ,则先进行分裂,再继续向下查找。

4. 直到查找到叶子节点,直接插入。(当然也有其它实现方式,比如每次插入后检查插入的节点是否需要分裂,沿着搜索路径回溯着向上融合)

我们先将分裂和向上融合的放法定义出来:

/*** @Author Nxy

* @Date 2020/2/23 20:49

* @Param 分裂满子结点,fatherNode:待分裂节点的父节点,splitNode:待分裂节点,index:待分裂节点在父节点中的索引

* @Return

* @Exception

* @Description 满子节点的分裂过程:从中间节点断开,后半部分形成新结点插入父节点。若分裂节点不是叶子节点,将子节点一并分裂到新节点*/

private void splitNode(Node fatherNode, Node splitNode, intindex) {//分裂产生的新节点

Node newNode = new Node(this.kComparator);//如果原节点为叶子节点,那么新节点也是

newNode.setIsLeaf(splitNode.isLeaf);//将 t到2*t-2 项迁移到新节点

for (int i = t; i < this.nodeMaxSize; i++) {

newNode.entrys.add(splitNode.entrys.get(i));

}//中间节点向上融合到父节点的 index+1

Entry midEntry = splitNode.entrys.get(t - 1);for (int i = this.nodeMaxSize - 1; i >= t - 1; i--) {//删除原节点中已迁移的项,删除时注意从尾部向前删除

splitNode.entrys.remove(i);

}//如果分裂的节点不是叶子节点,子节点一并跟随分裂

if (!splitNode.getIsLeaf()) {for (int i = t; i < this.nodeMaxSize + 1; i++) {

newNode.sons.add(splitNode.sons.get(i));

}//删除时注意从尾部向前删除

for (int i = this.nodeMaxSize; i >= t; i--) {

splitNode.sons.remove(i);

}

}//父节点插入分裂的中间元素,分裂出的新节点加入父节点的 sons

fatherNode.insertEntry(midEntry);

fatherNode.insertChild(newNode, index+ 1);

}

然后定义插入方法:

/*** @Author Nxy

* @Date 2020/2/23 23:53

* @Param root:当前节点,entry:待插入元素

* @Return

* @Exception

* @Description 插入一个非满节点:一路向下寻找插入位置。

* 在寻找的路径上,如果碰到大小为2t-1的节点,分裂并向上融合。

* 每次插入都从叶子节点插入,通过分裂将插入动作向上反馈,直到融合到根节点,只有由根节点的分裂

* 才能增加整棵树的高度,从而维持树的平衡。

* 树在一开始就是平衡的(只有根),整棵树的高度增加必须由根节点的分裂引发,从而高度增加后还是平衡的

* 因为没次检查子节点前如果子节点满了会先分裂,所以除根节点外,其余节点被其子节点向上融合均不会导致节点满

* 仅插入一个元素的情况下,每个节点最多经历一次子节点的分裂*/

private boolean insertNotFull(Node root, Entryentry) {if(root.getIsLeaf()) {//到达叶子节点,直接插入

returnroot.insertEntry(entry);

}

SearchResult re =root.search(entry.getKey());if(re.isExist) {//已存在key,直接返回

return false;

}int index =re.getIndex();

Node searchChild =root.childAt(index);//待查询子节点已满,分裂后再判断该搜索哪个子节点

if (searchChild.nodeSize() == 2 * t - 1) {

splitNode(root, searchChild, index);if (root.compare(root.entryAt(index).getKey(), entry.getKey()) > 0) {

searchChild= root.childAt(index + 1);

}

}returninsertNotFull(searchChild, entry);

}//插入一个新节点

public boolean insertNode(Entryentry) {//根节点满了,先分裂根节点

if (root.nodeSize() == 2 * t - 1) {

Node newRoot = new Node();

newRoot.setIsLeaf(false);

newRoot.insertChild(root,0);

splitNode(newRoot, root,0);this.root =newRoot;

}returninsertNotFull(root, entry);

}

B树的删除

删除比较麻烦,因为为了保证删除后树的结构依然符合定义,我们需要考虑非常多的情况。过程如下:

在本节点内查找删除项,找到了:

如果本节点叶子节点,直接删除。

如果不是叶子节点,则为了保证树结构,不可直接删除,继续判断:

1.尝试与子节点互换元素以维护树的结构

(首先是尝试将该数据项与子节点中的数据项互换)

如果该数据项的左子节点有大于等于 t 个元素,则将左子节点最后一个元素与删除元素互换位置。

左子节点元素个数少于 t ,检查右子节点,如果右子节点元素数大于等于 t ,将右子节点第一个元素与待删除元素互换位置。

2. 尝试与左右子节点合并

方案 1 没起作用,说明待删除元素的左右子节点元素个数都小于 t ,那么将其与待删除元素合并为一个大节点,长度将小于等于 2t-1 ,依然符合 B 树的定义。

那么我们将待删除元素合并为左子节点最后一个元素,右子节点携带其子元素一并合并入左子节点中。递归的对合并后节点进行处理。

在本节点内未找到待删除元素,去子节点继续寻找:

如果本节点为叶子节点,无子节点,直接返回待删除节点不在树中。

否则判断子节点是否可删除元素,如果子节点元素个数大于等于 t :

1.直接递归的处理子节点。

如果子节点元素个数小于 t ,不可删除项:

2.尝试让子节点的左右兄弟节点旋转来匀出一个元素给子节点

如果左兄弟可以匀出元素,左兄弟最后一个元素 - 本节点以子节点为右子节点的元素 - 子节点 进行右旋,同时将左兄弟最后一个元素的其右子树加到子节点儿子列表的开头。

如果右兄弟可以匀出元素,右兄弟第一个元素 - 本节点以子节点为左子节点的元素-子节点 进行左旋,同时将右兄弟第一个子元素加到子节点儿子列表的结尾。

3. 尝试合并子节点与其左/右兄弟节点

如果子节点有左兄弟节点,本节点中将以子节点为右子节点的元素 - 子节点合并入子节点的左兄弟,子节点的孩子列表追加到左子元素孩子列表的结尾。

如果子节点有右兄弟节点,本节点中以子节点为左子节点的元素 - 右兄弟节点合并入子节点,右兄弟节点的孩子列表追加到本节点孩子列表的结尾。

以上便是删除的逻辑过程,看起来情况比较多,但并不复杂。核心便是在删除时,为了不破坏树的结构,我们必须将删除元素交换到叶子节点后才能删除。而在交换过程中,不能使删除路径上节点的元素数小于 t-1(包括叶子节点)。

下面是实现:

private Entry delete(Node root, Entryentry) {

SearchResult re =root.search(entry.getKey());if(re.isExist()) {//回归条件,如果是叶子节点中的元素,直接删除

if(root.getIsLeaf()) {returnroot.removeEntry(re.getIndex());

}//如果不是叶子节点,判断应将待删除节点交换到左子节点还是右子节点

Node leftChild =root.childAt(re.getIndex());//如果左子节点包含多于 t-1 个项,转移到左子节点删除

if (leftChild.nodeSize() >=t) {//删除过程为,将待删除项与其左子节点最后一项互换,并递归互换下去,直到将待删除节点换到叶子节点后删除

root.removeEntry(re.getIndex());

root.insertEntry(leftChild.entryAt(leftChild.nodeSize()- 1), re.getIndex());

leftChild.removeEntry(leftChild.nodeSize()- 1);

leftChild.insertEntry(entry);returndelete(leftChild, entry);

}//左子节点不可删除项,则同样逻辑检查右子节点

Node rightChild = root.childAt(re.getIndex() + 1);if (rightChild.nodeSize() >=t) {

root.removeEntry(re.getIndex());

root.insertEntry(rightChild.entryAt(0), re.getIndex());

rightChild.removeEntry(0);

rightChild.insertEntry(entry);returndelete(rightChild, entry);

}//如果左右子节点均不能删除项,将左右子节点合并,并将删除项放到新节点的合并连接处

Entry deletedEntry =root.removeEntry(re.getIndex());

leftChild.insertEntry(deletedEntry);

root.removeChild(re.getIndex()+ 1);//左右子节点合并

for (int i = 0; i < rightChild.nodeSize(); i++) {

leftChild.insertEntry(rightChild.entryAt(i));

}//右子节点存在子节点,则子节点也合并入左子节点子节点集合

if (!rightChild.getIsLeaf()) {for (int i = 0; i < rightChild.sons.size(); i++) {

leftChild.insertChild(rightChild.childAt(i), leftChild.sons.size());

}

}//合并后继续向左递归

returndelete(leftChild, entry);

}else {//删除节点不在本节点//回归条件,搜索到叶节点依然没找到,待删除节点不在树中

if(root.getIsLeaf()) {for (int i = 0; i < root.nodeSize(); i++) {

System.out.print("++++++++++++++++++++");

System.out.print(root.entryAt(i).getKey()+ ",");

System.out.print("++++++++++++++++++++");

}throw new RuntimeException(entry.key + " is not in this tree!");

}

Node searchChild =root.childAt(re.index);//子节点可删除项,递归删除

if (searchChild.nodeSize() >=t) {returndelete(searchChild, entry);

}//待旋转节点,子节点项数小于等于 t-1 ,不能删除项,准备左旋或右旋为其补充项数

Node siblingNode = null;int siblingIndex = -1;//存在右兄弟

if (re.getIndex() < root.nodeSize() - 1) {

Node rightBrother = root.childAt(re.getIndex() + 1);if (rightBrother.nodeSize() >=t) {

siblingNode=rightBrother;

siblingIndex= re.getIndex() + 1;

}

}//不存在右兄弟则尝试左兄嘚

if (siblingNode == null) {if (re.getIndex() > 0) {//尝试左兄弟节点

Node leftBrothr = root.childAt(re.getIndex() - 1);if (leftBrothr.nodeSize() >=t) {

siblingNode=leftBrothr;

siblingIndex= re.getIndex() - 1;

}

}

}//至少有一个兄弟可以匀出项来

if (siblingNode != null) {//是左兄嘚

if (siblingIndex

searchChild.insertEntry(root.entryAt(siblingIndex), 0);

root.removeEntry(siblingIndex);

root.insertEntry(siblingNode.entryAt(siblingNode.nodeSize()- 1), siblingIndex);

siblingNode.removeEntry(siblingNode.nodeSize()- 1);//子节点跟着右旋

if (!siblingNode.getIsLeaf()) {

searchChild.insertChild(siblingNode.childAt(siblingNode.sons.size()- 1), 0);

siblingNode.removeChild(siblingNode.sons.size()- 1);

}

}else{//是右兄嘚

searchChild.insertEntry(root.entryAt(re.getIndex()), searchChild.nodeSize() - 1);

root.removeEntry(re.getIndex());

root.insertEntry(siblingNode.entryAt(0), re.getIndex());

siblingNode.removeEntry(0);if (!siblingNode.getIsLeaf()) {

searchChild.insertChild(siblingNode.childAt(0), searchChild.sons.size());

siblingNode.removeChild(0);

}

}returndelete(searchChild, entry);

}//左右兄嘚都匀不出项来,直接由左右兄嘚节点与父项合并为一个节点

if (re.getIndex() <= root.nodeSize() - 1) {

Node rightSon = root.childAt(re.getIndex() + 1);

searchChild.insertEntry(root.entryAt(re.getIndex()), searchChild.nodeSize());

root.removeEntry(re.getIndex());

root.removeChild(re.getIndex()+ 1);for (int i = 0; i < rightSon.nodeSize(); i++) {

searchChild.insertEntry(rightSon.entryAt(i));

}if (!rightSon.getIsLeaf()) {for (int j = 0; j < rightSon.sons.size(); j++) {

searchChild.insertChild(rightSon.childAt(j), searchChild.sons.size());

}

}if (root == this.root) {this.root =searchChild;

}

}else{//没有右兄弟,试试左兄嘚

Node leftSon = root.childAt(re.getIndex() - 1);

searchChild.insertEntry(root.entryAt(re.getIndex()- 1), 0);

root.removeChild(re.getIndex()- 1);

root.removeEntry(re.getIndex()- 1);for (int i = 0; i < leftSon.nodeSize(); i++) {

searchChild.insertEntry(leftSon.entryAt(i));

}if (!leftSon.getIsLeaf()) {for (int i = leftSon.sons.size() - 1; i >= 0; i--) {

searchChild.insertChild(leftSon.childAt(i),0);

}

}if (root == this.root) {this.root =searchChild;

}

}//if (root == this.root && root.nodeSize() == 0) {//root = searchChild;//}

returndelete(searchChild, entry);

}

}

下面放 B 树实现的整体代码:

packageBTree;import java.util.*;/*** @Author Nxy

* @Date 2020/2/23 14:04

* @Description B树实现*/

public class BTree{/*** @Author Nxy

* @Date 2020/2/23 14:06

* @Description 内部类,B树中节点中的元素。K:键类型,V:值类型,可以是指向数据的索引,也可以是实体数据*/

private class Entry{privateK key;privateV value;public voidsetKey(K key) {this.key =key;

}publicK getKey() {return this.key;

}public voidsetValue(V value) {this.value =value;

}publicV getValue() {return this.value;

}

@OverridepublicString toString() {return "key: " + this.key + " , ";

}

}/*** @Author Nxy

* @Date 2020/2/23 14:12

* @Description 内部类,封装搜索结果*/

private class SearchResult{private booleanisExist;privateV value;private intindex;//构造方法,将查询结果封装入对象

public SearchResult(boolean isExist, intindex, V value) {this.isExist =isExist;this.index =index;this.value =value;

}public booleanisExist() {returnisExist;

}publicV getValue() {returnvalue;

}public intgetIndex() {returnindex;

}

}/*** @Author Nxy

* @Date 2020/2/23 14:28

* @Description 树的节点*/

public class Node{//节点内的项

private List>entrys;//节点的孩子节点们

private List>sons;//是否是叶子节点

private booleanisLeaf;//键值比较函数对象,如果采用倒序或者其它排序方式,传入该对象

private ComparatorkComparator;//比较两个key,如果没有传入自定义排序方式则采用默认的升序

private intcompare(K key1, K key2) {return this.kComparator == null ? ((Comparable) key2).compareTo(key1) : kComparator.compare(key1, key2);

}//普通构造函数

Node() {this.entrys = new LinkedList>();this.sons = new LinkedList>();this.isLeaf = false;

}//自定义K排序方式的构造函数

Node(ComparatorkComparator) {this();this.kComparator =kComparator;

}public void setIsLeaf(booleanisLeaf) {this.isLeaf =isLeaf;

}public booleangetIsLeaf() {return this.isLeaf;

}//返回本节点的项数

public intnodeSize() {return this.entrys.size();

}/*** @Author Nxy

* @Date 2020/2/23 15:19

* @Param key:待查找元素的key值

* @Return 查找结果封装入 SearchResult

* @Exception

* @Description 在本节点内查找元素, 本质就是一个有序数组的二分查找*/

public SearchResultsearch(K key) {int begin = 0;int end = this.nodeSize() - 1;//if (end == 0) {//return new SearchResult(false, 0, null);//}

int mid = (begin + end) / 2;boolean isExist = false;int index = 0;

V value= null;//二分查找

while (begin

mid= (begin + end) / 2;

Entry midEntry= this.entrys.get(mid);int compareRe =compare((K) midEntry.getKey(), key);//找到了

if (compareRe == 0) {break;

}else{if (compareRe > 0) {//在中点右边

begin = mid + 1;

}else{

end= mid - 1;

}

}

}//二分查找结束,判断结果;三个元素以上才是正经二分,只有两个或一个元素属于边界条件要着重考虑

if (begin

isExist = true;

index=mid;

value= this.entrys.get(mid).getValue();

}else if (begin ==end) {

K midKey= this.entrys.get(begin).getKey();int comRe =compare(midKey, key);if (comRe == 0) {

isExist= true;

index=begin;

value= this.entrys.get(mid).getValue();

}else if (comRe > 0) {

isExist= false;

index= begin + 1;

value= null;

}else{

isExist= false;

index=begin;

value= null;

}

}else{

isExist= false;

index=begin;

value= null;

}return new SearchResult(isExist, index, value);

}//删除给定索引位置的项

public Entry removeEntry(intindex) {

Entry re = this.entrys.get(index);this.entrys.remove(index);returnre;

}//得到index处的项

public Entry entryAt(intindex) {return this.entrys.get(index);

}//将新项插入指定位置

private void insertEntry(Entry entry, intindex) {this.entrys.add(index, entry);

}//节点内插入项

private boolean insertEntry(Entryentry) {

SearchResult result =search(entry.getKey());if(result.isExist()) {return false;

}else{

insertEntry(entry, result.getIndex());return true;

}

}//更新项,如果项存在,更新其值并返回原值,否则直接插入

public V putEntry(Entryentry) {

SearchResult re =search(entry.getKey());if(re.isExist) {

Entry oldEntry= this.entrys.get(re.getIndex());

V oldValue=(V) oldEntry.getValue();

oldEntry.setValue(entry.getValue());returnoldValue;

}else{

insertEntry(entry);return null;

}

}//获得指定索引的子节点

public Node childAt(intindex) {return this.sons.get(index);

}//删除给定索引的子节点

public void removeChild(intindex) {this.sons.remove(index);

}//将新的子节点插入到指定位置

public void insertChild(Node child, intindex) {this.sons.add(index, child);

}

}//度数T,不传入则默认为 2-3 树

private Integer DEFAULT_T = 2;//根节点

private Noderoot;private int t =DEFAULT_T;//非根节点的最小项数,体现的是除了根节点,其余节点都是分裂而来的!

private int nodeMinSize = t - 1;//节点的最大项数

private int nodeMaxSize = 2 * t - 1;//比较函数对象

private ComparatorkComparator;//构造一棵自然排序的B树

BTree() {

Node root = new Node();this.root =root;

root.setIsLeaf(true);

}//构造一棵度为 t 的B树

BTree(intt) {this();this.t =t;

nodeMinSize= t - 1;

nodeMaxSize= 2 * t - 1;

}//构造一棵按给定排序方式排序,且度为 t 的B树

BTree(Comparator com, intt) {this(t);this.kComparator =com;

}//在以root为根的树内搜索key项

private V search(Noderoot, K key) {

SearchResult re =root.search(key);if(re.isExist) {returnre.value;

}else{//回归条件

if(root.isLeaf) {return null;

}int index =re.index;//递归搜索子节点

return(V) search(root.childAt(index), key);

}

}publicV search(K key) {return search(this.root, key);

}/*** @Author Nxy

* @Date 2020/2/23 20:49

* @Param 分裂满子结点,fatherNode:待分裂节点的父节点,splitNode:待分裂节点,index:待分裂节点在父节点中的索引

* @Return

* @Exception

* @Description 满子节点的分裂过程:从中间节点断开,后半部分形成新结点插入父节点。若分裂节点不是叶子节点,将子节点一并分裂到新节点*/

private void splitNode(Node fatherNode, Node splitNode, intindex) {//分裂产生的新节点

Node newNode = new Node(this.kComparator);//如果原节点为叶子节点,那么新节点也是

newNode.setIsLeaf(splitNode.isLeaf);//将 t到2*t-2 项迁移到新节点

for (int i = t; i < this.nodeMaxSize; i++) {

newNode.entrys.add(splitNode.entrys.get(i));

}//中间节点向上融合到父节点的 index+1

Entry midEntry = splitNode.entrys.get(t - 1);for (int i = this.nodeMaxSize - 1; i >= t - 1; i--) {//删除原节点中已迁移的项,删除时注意从尾部向前删除

splitNode.entrys.remove(i);

}//如果分裂的节点不是叶子节点,子节点一并跟随分裂

if (!splitNode.getIsLeaf()) {for (int i = t; i < this.nodeMaxSize + 1; i++) {

newNode.sons.add(splitNode.sons.get(i));

}//删除时注意从尾部向前删除

for (int i = this.nodeMaxSize; i >= t; i--) {

splitNode.sons.remove(i);

}

}//父节点插入分裂的中间元素,分裂出的新节点加入父节点的 sons

fatherNode.insertEntry(midEntry);

fatherNode.insertChild(newNode, index+ 1);

}/*** @Author Nxy

* @Date 2020/2/23 23:53

* @Param root:当前节点,entry:待插入元素

* @Return

* @Exception

* @Description 插入一个非满节点:一路向下寻找插入位置。

* 在寻找的路径上,如果碰到大小为2t-1的节点,分裂并向上融合。

* 每次插入都从叶子节点插入,通过分裂将插入动作向上反馈,直到融合到根节点,只有由根节点的分裂

* 才能增加整棵树的高度,从而维持树的平衡。

* 树在一开始就是平衡的(只有根),整棵树的高度增加必须由根节点的分裂引发,从而高度增加后还是平衡的

* 因为没次检查子节点前如果子节点满了会先分裂,所以除根节点外,其余节点被其子节点向上融合均不会导致节点满

* 仅插入一个元素的情况下,每个节点最多经历一次子节点的分裂*/

private boolean insertNotFull(Node root, Entryentry) {if(root.getIsLeaf()) {//到达叶子节点,直接插入

returnroot.insertEntry(entry);

}

SearchResult re =root.search(entry.getKey());if(re.isExist) {//已存在key,直接返回

return false;

}int index =re.getIndex();

Node searchChild =root.childAt(index);//待查询子节点已满,分裂后再判断该搜索哪个子节点

if (searchChild.nodeSize() == 2 * t - 1) {

splitNode(root, searchChild, index);if (root.compare(root.entryAt(index).getKey(), entry.getKey()) > 0) {

searchChild= root.childAt(index + 1);

}

}returninsertNotFull(searchChild, entry);

}//插入一个新节点

public boolean insertNode(Entryentry) {//根节点满了,先分裂根节点

if (root.nodeSize() == 2 * t - 1) {

Node newRoot = new Node();

newRoot.setIsLeaf(false);

newRoot.insertChild(root,0);

splitNode(newRoot, root,0);this.root =newRoot;

}returninsertNotFull(root, entry);

}/*** @Author Nxy

* @Date 2020/2/24 14:00

* @Param

* @Return

* @Exception

* @Description 如果Key已存在,更新value,否则直接插入entry*/

private V putNotFull(Node root, Entryentry) {assert root.nodeSize()

}

SearchResult re =root.search(entry.getKey());if(re.isExist) {//如果存在,则更新

root.entryAt(re.index).setValue(entry.getValue());returnre.value;

}//如果不存在,继续向下搜素,先判断子节点是否需要分裂

Node searchChild =root.childAt(re.index);if (searchChild.nodeSize() == 2 * t - 1) {

splitNode(root, searchChild, re.index);if (root.compare(entry.getKey(), root.entryAt(re.index).getKey()) > 0) {

searchChild= root.childAt(re.index + 1);

}

}returnputNotFull(searchChild, entry);

}//如果树中已存在 key 则更新并返回原 value,否则插入并返回null

public V put(Entryentry) {//如果根节点已满,先分裂根节点

if (this.root.nodeSize() ==nodeMaxSize) {

Node newRoot = new Node(kComparator);

newRoot.setIsLeaf(false);

newRoot.insertChild(root,0);

splitNode(newRoot, root,0);this.root =newRoot;

}returnputNotFull(root, entry);

}private Entry delete(Node root, Entryentry) {

SearchResult re =root.search(entry.getKey());if(re.isExist()) {//回归条件,如果是叶子节点中的元素,直接删除

if(root.getIsLeaf()) {returnroot.removeEntry(re.getIndex());

}//如果不是叶子节点,判断应将待删除节点交换到左子节点还是右子节点

Node leftChild =root.childAt(re.getIndex());//如果左子节点包含多于 t-1 个项,转移到左子节点删除

if (leftChild.nodeSize() >=t) {//删除过程为,将待删除项与其左子节点最后一项互换,并递归互换下去,直到将待删除节点换到叶子节点后删除

root.removeEntry(re.getIndex());

root.insertEntry(leftChild.entryAt(leftChild.nodeSize()- 1), re.getIndex());

leftChild.removeEntry(leftChild.nodeSize()- 1);

leftChild.insertEntry(entry);returndelete(leftChild, entry);

}//左子节点不可删除项,则同样逻辑检查右子节点

Node rightChild = root.childAt(re.getIndex() + 1);if (rightChild.nodeSize() >=t) {

root.removeEntry(re.getIndex());

root.insertEntry(rightChild.entryAt(0), re.getIndex());

rightChild.removeEntry(0);

rightChild.insertEntry(entry);returndelete(rightChild, entry);

}//如果左右子节点均不能删除项,将左右子节点合并,并将删除项放到新节点的合并连接处

Entry deletedEntry =root.removeEntry(re.getIndex());

leftChild.insertEntry(deletedEntry);

root.removeChild(re.getIndex()+ 1);//左右子节点合并

for (int i = 0; i < rightChild.nodeSize(); i++) {

leftChild.insertEntry(rightChild.entryAt(i));

}//右子节点存在子节点,则子节点也合并入左子节点子节点集合

if (!rightChild.getIsLeaf()) {for (int i = 0; i < rightChild.sons.size(); i++) {

leftChild.insertChild(rightChild.childAt(i), leftChild.sons.size());

}

}//合并后继续向左递归

returndelete(leftChild, entry);

}else {//删除节点不在本节点//回归条件,搜索到叶节点依然没找到,待删除节点不在树中

if(root.getIsLeaf()) {for (int i = 0; i < root.nodeSize(); i++) {

System.out.print("++++++++++++++++++++");

System.out.print(root.entryAt(i).getKey()+ ",");

System.out.print("++++++++++++++++++++");

}throw new RuntimeException(entry.key + " is not in this tree!");

}

Node searchChild =root.childAt(re.index);//子节点可删除项,递归删除

if (searchChild.nodeSize() >=t) {returndelete(searchChild, entry);

}//待旋转节点,子节点项数小于等于 t-1 ,不能删除项,准备左旋或右旋为其补充项数

Node siblingNode = null;int siblingIndex = -1;//存在右兄弟

if (re.getIndex() < root.nodeSize() - 1) {

Node rightBrother = root.childAt(re.getIndex() + 1);if (rightBrother.nodeSize() >=t) {

siblingNode=rightBrother;

siblingIndex= re.getIndex() + 1;

}

}//不存在右兄弟则尝试左兄嘚

if (siblingNode == null) {if (re.getIndex() > 0) {//尝试左兄弟节点

Node leftBrothr = root.childAt(re.getIndex() - 1);if (leftBrothr.nodeSize() >=t) {

siblingNode=leftBrothr;

siblingIndex= re.getIndex() - 1;

}

}

}//至少有一个兄弟可以匀出项来

if (siblingNode != null) {//是左兄嘚

if (siblingIndex

searchChild.insertEntry(root.entryAt(siblingIndex), 0);

root.removeEntry(siblingIndex);

root.insertEntry(siblingNode.entryAt(siblingNode.nodeSize()- 1), siblingIndex);

siblingNode.removeEntry(siblingNode.nodeSize()- 1);//子节点跟着右旋

if (!siblingNode.getIsLeaf()) {

searchChild.insertChild(siblingNode.childAt(siblingNode.sons.size()- 1), 0);

siblingNode.removeChild(siblingNode.sons.size()- 1);

}

}else{//是右兄嘚

searchChild.insertEntry(root.entryAt(re.getIndex()), searchChild.nodeSize() - 1);

root.removeEntry(re.getIndex());

root.insertEntry(siblingNode.entryAt(0), re.getIndex());

siblingNode.removeEntry(0);if (!siblingNode.getIsLeaf()) {

searchChild.insertChild(siblingNode.childAt(0), searchChild.sons.size());

siblingNode.removeChild(0);

}

}returndelete(searchChild, entry);

}//左右兄嘚都匀不出项来,直接由左右兄嘚节点与父项合并为一个节点

if (re.getIndex() <= root.nodeSize() - 1) {

Node rightSon = root.childAt(re.getIndex() + 1);

searchChild.insertEntry(root.entryAt(re.getIndex()), searchChild.nodeSize());

root.removeEntry(re.getIndex());

root.removeChild(re.getIndex()+ 1);for (int i = 0; i < rightSon.nodeSize(); i++) {

searchChild.insertEntry(rightSon.entryAt(i));

}if (!rightSon.getIsLeaf()) {for (int j = 0; j < rightSon.sons.size(); j++) {

searchChild.insertChild(rightSon.childAt(j), searchChild.sons.size());

}

}if (root == this.root) {this.root =searchChild;

}

}else{//没有右兄弟,试试左兄嘚

Node leftSon = root.childAt(re.getIndex() - 1);

searchChild.insertEntry(root.entryAt(re.getIndex()- 1), 0);

root.removeChild(re.getIndex()- 1);

root.removeEntry(re.getIndex()- 1);for (int i = 0; i < leftSon.nodeSize(); i++) {

searchChild.insertEntry(leftSon.entryAt(i));

}if (!leftSon.getIsLeaf()) {for (int i = leftSon.sons.size() - 1; i >= 0; i--) {

searchChild.insertChild(leftSon.childAt(i),0);

}

}if (root == this.root) {this.root =searchChild;

}

}//if (root == this.root && root.nodeSize() == 0) {//root = searchChild;//}

returndelete(searchChild, entry);

}

}public Entrydelete(K key) {

Entry en = new Entry();

en.setKey(key);returndelete(root, en);

}/*** @Author Nxy

* @Date 2020/2/25 14:18

* @Description 借助队列打印B树*/

public voidoutput() {

Queue> queue = new LinkedList>();

queue.offer(this.root);while (!queue.isEmpty()) {

Node node =queue.poll();for (int i = 0; i < node.nodeSize(); ++i) {

System.out.print(node.entryAt(i)+ " ");

}

System.out.println();if (!node.getIsLeaf()) {for (int i = 0; i <= node.sons.size() - 1; ++i) {

queue.offer(node.childAt(i));

}

}

}

}public static voidmain(String[] args) {

Random random= newRandom();

BTree btree = new BTree(3);

List save = new ArrayList(30);//save.add(8290);//save.add(7887);//save.add(9460);//save.add(9928);//save.add(6127);//save.add(5891);//save.add(1592);//save.add(14);//save.add(8681);//save.add(4843);//save.add(1051);

for (int i = 0; i < 20; ++i) {int r = random.nextInt(10000);

save.add(r);

System.out.print(r+ " ");

BTree.Entry en= btree.new Entry();

en.setKey(r);

en.setValue(r);//BTree.Entry en = btree.new Entry();//en.setKey(save.get(i));

btree.insertNode(en);

}

System.out.println("----------------------");

btree.output();

System.out.println("----------------------");

btree.delete(save.get(0));

btree.output();

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值