首先很高兴你看到了这篇文章,这篇文章可能会花费你很长很长的时间去看,但是这篇文章包括的内容绝对足够你对树的一个系统性的学习。为什么要写这篇文字呢?因为自己在学习树的时候,有些博客只有图解,有些博客只有代码,在这里我将树的图解和代码都整理在了一起。在每个树都有着对应的图解和代码。相信这篇文字会花费你大量的时间,但是绝对值得。
因为全部加在一起太长了,所以分成了三部分去记录:
超级详细树讲解一 —— 二叉树、二叉查找树、完全二叉树图解+代码
超级详细树讲解二 —— 平衡二叉树、哈夫曼树图解+代码
因为博客的排版问题,但是感觉还是比较丑,如果大家想看好看的版本,大家可以前往pdf传送门进行下载。
pdf包含如下树:
代码的github地址:代码传送门
pdf的github地址:pdf传送门
文章目录
B树
背景
-
为什么不用二叉树进行查找?二叉树的查找速度和比较次数都是最小的,但是现实的问题是:磁盘IO
-
当数据库的索引大小超过了几G,这时候不能把整个索引加载到内存,只能够逐一加载每一个磁盘页(也就是索引树节点)
情景假设此时假设我们用二叉查找树作为索引结构,假设树的高度是4,查找的值是10:
9 -> 13 -> 11 -> 10
磁盘IO次数就是由 树的深度决定的
定义
B树就是B-树,中间的横线不是减号(不可以读成B减树)。B树是一种多路平衡查找树,它的每个节点最多包含K个孩子,K被称为B树的阶。K的大小取决于磁盘页的大小。
下面来具体介绍一下B-树(Balance Tree),一个m阶的B树具有如下几个特征:
- 根结点至少有两个子女。
- 每个中间节点都包含k-1个元素和k个孩子,其中 m/2 <= k <= m
- 每一个叶子节点都包含k-1个元素,其中 m/2 <= k <= m
- 所有的叶子结点都位于同一层。
- 每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。
是不是很懵逼,看不下去,继续往下看:
3阶B树
查询5: 9 -> 2 6 -> 3 5
查询比较的次数相比于磁盘IO 的次数,内存中的消耗几乎可以忽略,所以只要树的高度足够低,IO次数足够少,就可提升查找性能。
优点
- 磁盘IO快
- 自平衡
引用
B-树主要应用于文件系统以及部分数据索引,比如著名的MongoDB。
大部分关系型数据库,比如Mysql则是使用B+树作为索引。
B树节点的定义
节点需要三个变量,分别是:
-
关键字
叶点信息的数量 就是一个节点可以存放的。每个节点的大小为一个磁盘的页,节点中锁包含的关键字及其子节点的数目取决于页的大小。
-
内结点的子节点
用于储存该节点的子节点信息 因为自己点不一定是只有一个,所以需要用一个链表来存储子节点信息
-
父节点
指向父节点的信息
其中除了简单的构造方法之外还需要加上如下方法:
addChildren(List<BTNode> children) 设置父节点批量添加多个孩子结点,然后排序
searchKey() 在结点node中搜索关键字key
addChild(BTNode child) 设置父节点批量添加单个孩子结点,然后排序
removeChild(BTNode childNode) 删除孩子结点
代码如下:
package src.Tree.BTree;
import java.util.*;
/**
* @Author : Auraros
* @Description :
* @Data : 2020-12-04 17:42
* @Version : 1.0
*/
public class BTNode {
private List<Integer> keys;
private List<BTNode> childNodes; //内结点的子结点
private BTNode parent;
public BTNode() {
keys = new LinkedList<Integer>();
childNodes = new ArrayList<BTNode>();
}
public BTNode(BTNode parent) {
this();
this.parent = parent;
}
public BTNode getParentNode() {
return parent;
}
public void setParentNode(BTNode parentNode) {
this.parent = parentNode;
}
public List<Integer> getKeys() {
return keys;
}
public Integer getKey(int index) {
return keys.get(index);
}
public void setKeys(List<Integer> keys) {
this.keys = keys;
}
public void setChildNodes(List<BTNode> childNodes) {
this.childNodes = childNodes;
}
public List<BTNode> getChildNodes() {
return childNodes;
}
public BTNode getChild(int index) {
return childNodes.get(index);
}
/**
* 设置父节点并批量添加孩子结点,添加后需要排序
*
* @param children
* @return boolean
*/
public boolean addChildren(List<BTNode> children) {
for (BTNode node : children) {
node.setParentNode(this);
}
childNodes.addAll(children);
//排序
Collections.sort(childNodes, new Comparator<BTNode>() {
@Override
public int compare(BTNode o1, BTNode o2) {
return o1.getKeys().get(0).compareTo(o2.getKeys().get(0));
}
});
return true;
}
public void addKey(Integer key) {
SearchResult searchResult = searchKey(this, key);
this.getKeys().add(searchResult.getIndex(), key);
}
/**
* 在结点node中搜索关键字key
* @param node BTNode
* @param key Integer
* @return
*/
public SearchResult searchKey(BTNode node, Integer key)
{
boolean result = false;
int index;
List<Integer> keys = node.getKeys();
int low = 0 ;
int high = keys.size() - 1;
int mid = 0;
while(low <= high)
{
mid = (low + high) >>> 1;
Comparable<Integer> midVal = keys.get(mid);
int cmp = midVal.compareTo(key);
if(cmp < 0)
low = mid + 1;
else if(cmp > 0)
high = mid - 1;
else {
break;
}
}
if(low <= high){//查找成功
result = true;
index = mid;
}else{
index = low;
}
return new SearchResult(result, index);
}
/**
* 设置父节点并添加孩子结点,添加后需要排序
* @param child
* @return boolean
*/
public boolean addChild(BTNode child)
{
child.setParentNode(this);
childNodes.add(child);
//排序
Collections.sort(childNodes, new Comparator<BTNode>() {
@Override
public int compare(BTNode o1, BTNode o2) {
return o1.getKeys().get(0).compareTo(o2.getKeys().get(0));
}
});
return true;
}
/**
* 删除孩子结点
* @param childNode
*/
public void removeChild(BTNode childNode) {
childNodes.remove(childNode);
}
/**
* @return 结点中关键字个数
*/
public int sizeOfKeys() {
return keys.size();
}
/**
* @return 孩子个数
*/
public int sizeOfChildren() {
return childNodes.size();
}
}
B树查找类定义
-
在B树节点中搜索给定键值的返回结果。
-
该结果由两部分组成。第一部分表示此次查找是否成功
-
如果查找成功,第二部分表示给定键值在B树节点中的位置
-
如果查找失败,第二部分表示给定键值应该插入的位置。
package src.Tree.BTree;
/**
* @Author : Auraros
* @Description :
* @Data : 2020-12-04 17:48
* @Version : 1.0
*/
public class SearchResult {
private boolean result;
private int index;
public SearchResult(boolean result, int index) {
super();
this.result = result; //是否查找成功
this.index = index; //指定键值
}
/**
* @return 给定键值在B树节点中的位置或者给定键值应该插入的位置
*/
public int getIndex() {
return index;
}
/**
* @return 查找是否成功
*/
public boolean getResult(){
return result;
}
}
B树阶数定义
定义如下:
1. k 阶数定义,比如3阶
2. minkey 最小关键字定义: ceil(k/2 - 1) 比如 1
3. maxkey 最大关键字定义: k-1 比如 2
4. minChild 最小的孩子个数: ceil(k/2) 比如 2
5. maxChild 最大的孩子个数: k 比如 3
package src.Tree.BTree;
/**
* @Author : Auraros
* @Description :
* @Data : 2020-12-04 17:49
* @Version : 1.0
*/
public class Constants {
/**
* BTree的阶<br>
* BTree中关键字个数为[ceil(m/2)-1,m-1] <br> 向上取整
* BTree中子树个数为[ceil(m/2),m]
*/
public static final int BTREE_ORDER = 3;
/**
* 非根节点中最小的关键字个数
*/
public static final int MIN_KEY_SIZE = (int) (Math.ceil(Constants.BTREE_ORDER / 2.0) - 1);
/**
* 非根节点中最大的关键字个数
*/
public static final int MAX_KEY_SIZE = Constants.BTREE_ORDER - 1;
/**
* 每个结点中最小的孩子个数
*/
public static final int MIN_CHILDREN_SIZE = (int) (Math.ceil(Constants.BTREE_ORDER / 2.0));
/**
* 每个结点中最大的孩子个数
*/
public static final int MAX_CHILDREN_SIZE = Constants.BTREE_ORDER ;
}
B树创建定义
主要函数如下:
newInstance() 初始化函数,创建一个B树
insertKey(Integer key) 插入关键字
removeKey(Integer key) 删除关键字
findNode(Integer key) 查找key函数
insertKeys(Integer[] keys) 插入关键字数组
B树插入实现
插入主要考虑两种情况:
- 是叶子节点
- 插入后如果超出最大个数,则分裂,否则则插入
- 不是叶子节点
- 找到对应区间的下一个结点,采用了二叉搜索的查找思想
- 调用是否是叶子节点
分裂主要如下图所示:
- 一棵B树如图所示
- 此时插入key值为26的节点
- 当前结点需要以27为中心分裂,并向父结点进位27,然后当前结点指向父结点,结果如下图所示。
- 进位后导致当前结点(即根结点)也需要分裂,分裂的结果如下图所示。
插入代码如下:
/**
* 向BTree中插入关键字<p>
* 从根节点开始寻找最佳的插入结点,若为叶子结点,则先插入key然后判断是否需要分裂;
* 若为非叶子结点,则从上到下寻找最佳的叶子结点,然后重复上面叶子结点的情况
* @param key Integer
* @return boolean 是否插入成功
*/
public boolean insertKey(Integer key){
BTNode node = root;
while (node != null){ //从根节点开始往下查找
if (node.sizeOfChildren() == 0){ //叶子节点
node.addKey(key); //因为每次加入都会进行排序所以不用担心
if (node.sizeOfKeys() <= Constants.MAX_KEY_SIZE){
break;
} else { //插入关键字之后其个数大于最大size,需要进行分裂
splitNode(node);
break;
}
} else { //非叶子结点
Integer lesser = node.getKey(0);
if (key.compareTo(lesser) < 0) { //比最小的关键字还小
node = node.getChild(0);//则关键字必定插入到它最左边的子树上
continue;
}
int size = node.sizeOfKeys();
int last = size - 1;
Integer greater = node.getKey(last);
if (key.compareTo(greater) > 0) {//比最大的关键字还大
node = node.getChild(size);//则关键字必定插入到它最右边的子树上
continue;
}
//若不属于上述两种情况则需要一步步查找中间结点
for (int i = 1; i < node.sizeOfKeys(); i++){
Integer prev = node.getKey(i - 1);
Integer next = node.getKey(i);
if (key.compareTo(prev) > 0 && key.compareTo(next) < 0)
{
node = node.getChild(i);
break;
}
}
}
}
sizeOfKeys ++;
return true;
}
分裂操作
1. 得到 node.sizeOfKeys() / 2的关键字,因为要从中间开始分裂
2. 生成一个新的左结点,将前一半的关键字存入,并且根据关键字大小接入子节点
3. 生成一个新的右节点,将后一半的关键字存入,并且根据关键字大小接入子节点
4. 判断有没有父节点,有的话则将node.sizeOfKeys() / 2的关键字插入,判断是否超过最大关键字数,是的话递归分裂;没有的话新建一个结点最为根结点。
代码如下:
/**
* 从叶子节点开始从下到上递归分裂
* @param node BTNode
* @param
*/
private void splitNode(BTNode node)
{
//分裂位置发生在size/2,分裂成两个结点
int splitIndex = node.sizeOfKeys() / 2;
//需要上移的关键字
Integer splitKey = node.getKeys().get(splitIndex);
//生成新的结点
BTNode left = new BTNode(null);
for(int i = 0 ; i < splitIndex; i ++)
{
left.addKey(node.getKey(i));
}
if(node.sizeOfChildren() > 0)
{
left.addChildren(node.getChildNodes().subList(0, splitIndex + 1));//这里索引需要注意
}
BTNode right = new BTNode(null);
for(int i = splitIndex + 1; i < node.sizeOfKeys(); i ++)
{
right.addKey(node.getKey(i));
}
if(node.sizeOfChildren() > 0)
{
right.addChildren(node.getChildNodes().subList(splitIndex + 1, node.sizeOfChildren()));
}
if(node.getParentNode() != null)//有父节点
{
BTNode parent = node.getParentNode();//取得其父节点
parent.addKey(splitKey);
parent.removeChild(node);
parent.addChild(left);
parent.addChild(right);
if(parent.sizeOfKeys() > Constants.MAX_KEY_SIZE)
{
splitNode(parent);
}
}else{//没有父节点,即到达根节点
BTNode newRoot = new BTNode(null);
newRoot.getKeys().add(splitKey);
root = newRoot;
newRoot.addChild(left);
newRoot.addChild(right);
}
}
B 树删除实现
首先,通过关键字找到在哪个节点中。
1. 该节点是非叶子节点:
(1) 删除其中的key值
(2) 左孩子最大结点中的最大的元素并删除(简称取过来)
(3) 添加到删除了key的节点中
(4) 判断左孩子节点会不会小于最下关键字个数,如果小则合并
2. 该节点是叶子节点:
(1)既是叶子结点又是根节点且 node.sizeOfKeys() < Constants.MIN_KEY_SIZE
(2)叶子结点但非根结点且node.sizeOfKeys() < Constants.MIN_KEY_SIZE ----->只有这一种需要合并
(3)既是叶子结点又是根节点且node.sizeOfKeys() > Constants.MIN_KEY_SIZE
(4)叶子结点但非根结点且node.sizeOfKeys() > Constants.MIN_KEY_SIZE
非叶子节点
在上述情况下接着删除27。从上图可知27位于非叶子结点中,所以用27的后继替换它。从图中可以看出,27的后继为28,我们用28替换27,然后在28(原27)的右孩子结点中删除28。删除后的结果如下图所示。
删除后发现,当前叶子结点的记录的个数小于2,而它的兄弟结点中有3个记录(当前结点还有一个右兄弟,选择右兄弟就会出现合并结点的情况,不论选哪一个都行,只是最后B树的形态会不一样而已),我们可以从兄弟结点中借取一个key。所以父结点中的28下移,兄弟结点中的26上移,删除结束。
删除为叶子节点(只写需要合并的情况)
在上述情况下接着32
当删除后,当前结点中只key,而兄弟结点中也仅有2个key。所以只能让父结点中的30下移和这个两个孩子结点中的key合并,成为一个新的结点,当前结点的指针指向父结点。
代码如下:
public boolean removeKey(Integer key)
{
//先找到关键字在哪个结点中,如果没有找到此结点则直接返回false
BTNode node = this.findNode(key);
if(node == null)
return false;
int index = node.getKeys().indexOf(key);//获取此key的索引
node.getKeys().remove(key);//删除此key
if(node.sizeOfChildren() != 0)//非叶子结点
{
BTNode left = node.getChild(index);//左孩子
BTNode greatest = this.getGreatestNode(left);
//获取并删除左孩子最大结点中的最大的元素
Integer replaceValue = greatest.getKeys().remove(greatest.sizeOfKeys() - 1);
//将此元素添加到删除关键字key的结点中
node.addKey(replaceValue);
//如果删除关键字后导致下溢,则需要合并结点
if(greatest.sizeOfKeys() < Constants.MIN_KEY_SIZE)
{
this.combined(greatest); //greatest为叶子结点
}
}else{//叶子结点
//分为四种情况:
//1)既是叶子结点又是根节点且 node.sizeOfKeys() < Constants.MIN_KEY_SIZE
//2)叶子结点但非根结点且node.sizeOfKeys() < Constants.MIN_KEY_SIZE ----->只有这一种需要合并
//3)既是叶子结点又是根节点且node.sizeOfKeys() > Constants.MIN_KEY_SIZE
//4)叶子结点但非根结点且node.sizeOfKeys() > Constants.MIN_KEY_SIZE
if(node.getParentNode() != null && node.sizeOfKeys() < Constants.MIN_KEY_SIZE)
{
this.combined(node);
}else if(node.getParentNode() == null && node.sizeOfKeys() == 0)
{
//删除的是最后一个元素
root = null;
}
}
sizeOfKeys --;
return true;
}
合并操作
根据结点找到对应的左邻居右邻居
1. 此结点的右邻居存在且右邻居结点中关键字个数 >= minSize + 1 相当于左旋
2. 此结点的左邻居存在且左邻居结点中关键字个数 >= minSize + 1 相当于右旋
3. 此结点的右邻居存在且父节点关键字个数大于0 结点合并
4. 此结点的左邻居存在且父节点关键字个数大于0 结点合并
代码如下:
/**
* 此node结点中关键字个数不足,需要合并结点,与splitNode()方法类似,也是从叶节点开始向上递归的。<p>
* 需要分情况讨论: <br>
* 1) 此结点的右邻居存在且右邻居结点中关键字个数 >= minSize + 1 相当于左旋
* 2) 此结点的左邻居存在且左邻居结点中关键字个数 >= minSize + 1 相当于右旋
* 3) 此结点的右邻居存在且父节点关键字个数大于0 结点合并
* 4) 此结点的左邻居存在且父节点关键字个数大于0 结点合并
* @param node BTNode
*/
private void combined(BTNode node)
{
//先获取此结点的父节点
BTNode parentNode = node.getParentNode();
//获取此结点是其父节点中的索引,即第几个孩子
int index = parentNode.getChildNodes().indexOf(node);
int indexOfLeftNeighbor = index - 1;
int indexOfRightNeighbor = index + 1;
BTNode rightNeighbor = null;
int rightNeighborSize = 0;
if(indexOfRightNeighbor < parentNode.sizeOfChildren())//右邻居存在
{
rightNeighbor = parentNode.getChild(indexOfRightNeighbor);
rightNeighborSize = rightNeighbor.sizeOfKeys();
}
//右邻居存在且其关键字个数大于最小值
if(rightNeighbor != null && rightNeighborSize > Constants.MIN_KEY_SIZE)
{
//相当于左旋
Integer removeValue = rightNeighbor.getKeys().remove(0);
int prev = getIndexOfPreviousValue(parentNode, removeValue);
Integer parentValue = parentNode.getKeys().remove(prev);
node.addKey(parentValue);
parentNode.addKey(removeValue);
if(rightNeighbor.sizeOfChildren() > 0)//如果右邻居的孩子结点存在,则需要把右邻居的第一个孩子结点删除并添加到node结点中
{
node.addChild(rightNeighbor.getChildNodes().remove(0));
}
}else{
BTNode leftNeighbor = null;
int leftNeighborSize = 0;//???
if(indexOfLeftNeighbor >= 0)//左邻居存在
{
leftNeighbor = parentNode.getChild(indexOfLeftNeighbor);
leftNeighborSize = leftNeighbor.sizeOfKeys();
}
//左邻居存在且其关键字个数大于最小值
if(leftNeighbor != null && leftNeighborSize > Constants.MIN_KEY_SIZE)//左邻居存在且其关键字个数大于最小值
{
//相当于右旋
Integer removeValue = leftNeighbor.getKeys().remove(leftNeighbor.sizeOfKeys() - 1);
int next = getIndexOfNextValue(parentNode, removeValue);
Integer parentValue = parentNode.getKeys().remove(next);
node.addKey(parentValue);
parentNode.addKey(removeValue);
if(leftNeighbor.sizeOfChildren() > 0)//如果左邻居的孩子结点存在,则需要把右邻居的最后一个孩子结点删除并添加到node结点中
{
node.addChild(leftNeighbor.getChildNodes().remove(leftNeighbor.sizeOfChildren() - 1));
}
}else if(rightNeighbor != null && parentNode.sizeOfKeys() > 0)//右邻居存在且父节点关键字个数大于0
{
Integer rightValue = rightNeighbor.getKey(0);//获取右邻居结点中最左边的关键字
int prev = getIndexOfPreviousValue(parentNode, rightValue);//获取rightValue关键字的父节点中不大于但最接近此关键字的索引
Integer parentKey = parentNode.getKeys().remove(prev);//在父节点中删除此索引对应的关键字
parentNode.removeChild(rightNeighbor); // 在父节点中删除此索引对应的孩子结点
node.addKey(parentKey);//将删除的关键字添加到关键字下溢的结点中
//将右邻居中的关键字添加进去
for(int i = 0 ; i < rightNeighbor.sizeOfKeys(); i ++)
{
node.addKey(rightNeighbor.getKey(i));
}
//将右邻居中的孩子结点也添加进去
for(int i = 0 ; i < rightNeighbor.sizeOfChildren(); i ++)
{
node.addChild(rightNeighbor.getChild(i));
}
if(parentNode.getParentNode() != null && parentNode.sizeOfKeys() < Constants.MIN_KEY_SIZE)//还没到达根节点
{
this.combined(parentNode);
}else if(parentNode.sizeOfKeys() == 0){
//父节点中没有关键字了,则降低树的高度
node.setParentNode(null);//注意:树的高度降低一层,此结点就变为根节点,一定要设置其父节点为null
parentNode = null;
root = node;
}
}else if(leftNeighbor != null && parentNode.sizeOfKeys() > 0) //左邻居存在且父节点关键字个数大于0
{
Integer leftValue = leftNeighbor.getKey(leftNeighbor.sizeOfKeys() - 1);//获取左邻居结点中最右边的关键字
int next = getIndexOfNextValue(parentNode, leftValue);//获取leftValue关键字的父节点中不小于但最接近此关键字的索引
Integer parentKey = parentNode.getKeys().remove(next);//在父节点中删除此索引对应的关键字
parentNode.removeChild(leftNeighbor); //在父节点中删除此索引对应的孩子结点
node.addKey(parentKey);//将删除的关键字添加到关键字下溢的结点中
//将左邻居中的关键字添加进去
for(int i = 0 ; i < leftNeighbor.sizeOfKeys(); i ++)
{
node.addKey(leftNeighbor.getKey(i));
}
//将左邻居中的孩子结点也添加进去
for(int i = 0 ; i < leftNeighbor.sizeOfChildren(); i ++)
{
node.addChild(leftNeighbor.getChild(i));
}
if(parentNode.getParentNode() != null && parentNode.sizeOfKeys() < Constants.MIN_KEY_SIZE)//还没到达根节点
{
this.combined(parentNode);
}else if(parentNode.sizeOfKeys() == 0){
//父节点中没有关键字了,则降低树的高度
node.setParentNode(null);//注意:树的高度降低一层,此结点就变为根节点,一定要设置其父节点为null
parentNode = null;
root = node;
}
}
}//end else
}
所有代码如下:
package src.Tree.BTree;
import java.util.List;
/**
* @Author : Auraros
* @Description :
* @Data : 2020-12-04 17:49
* @Version : 1.0
*/
public class BTree {
private BTNode root; //Btree的根节点
private int sizeOfKeys; //Btree中关键字个数
/**
* 默认情况下只有一个节点且为叶子结点
*/
private BTree()
{
root = new BTNode(null);//根节点没有父节点
}
private static BTree instance = new BTree();
/**
* 创建BTree
* @return BTree
*/
public static BTree newInstance()
{
return instance;
}
/**
* 向BTree中插入关键字<p>
* 从根节点开始寻找最佳的插入结点,若为叶子结点,则先插入key然后判断是否需要分裂;
* 若为非叶子结点,则从上到下寻找最佳的叶子结点,然后重复上面叶子结点的情况
* @param key Integer
* @return boolean 是否插入成功
*/
public boolean insertKey(Integer key)
{
BTNode node = root;
while (node != null) //从根节点开始往下查找
{
if (node.sizeOfChildren() == 0) //叶子节点
{
node.addKey(key);
if (node.sizeOfKeys() <= Constants.MAX_KEY_SIZE)
{
break;
} else {//插入关键字之后其个数大于最大size,需要进行分裂
splitNode(node);
break;
}
} else {//非叶子结点
Integer lesser = node.getKey(0);
if (key.compareTo(lesser) < 0) {//比最小的关键字还小
node = node.getChild(0);//则关键字必定插入到它最左边的子树上
continue;
}
int size = node.sizeOfKeys();
int last = size - 1;
Integer greater = node.getKey(last);
if (key.compareTo(greater) > 0) {//比最大的关键字还大
node = node.getChild(size);//则关键字必定插入到它最右边的子树上
continue;
}
//若不属于上述两种情况则需要一步步查找中间结点
for (int i = 1; i < node.sizeOfKeys(); i++)
{
Integer prev = node.getKey(i - 1);
Integer next = node.getKey(i);
if (key.compareTo(prev) > 0 && key.compareTo(next) < 0)
{
node = node.getChild(i);
break;
}
}
}
}
sizeOfKeys ++;
return true;
}
/**
* 删除BTree中关键字。先找到关键字在哪个结点中,如果没有找到此结点则直接返回false,
* 如果此结点存在再对该结点分叶子结点和非叶子结点讨论:<br>
* <br>
* 对于叶子结点:<br>
* 1)既是叶子结点又是根节点且 node.sizeOfKeys() < Constants.MIN_KEY_SIZE<br>
* 2)叶子结点但非根结点且node.sizeOfKeys() < Constants.MIN_KEY_SIZE ----->只有这一种需要合并<br>
* 3)既是叶子结点又是根节点且node.sizeOfKeys() > Constants.MIN_KEY_SIZE<br>
* 4)叶子结点但非根结点且node.sizeOfKeys() > Constants.MIN_KEY_SIZE<br>
* 5)实际只需讨论两种情况<br>
* <br>
* 对于非叶子结点:<br>
* 1)先获取关键字key在所在的结点中的关键字列表keys中的索引,删除该关键字key<br>
* 2)获取并删除左孩子最大结点greatest中的最大的元素,此元素在叶节点中,即用左孩子中最大的关键字填补被删除的关键字key<br>
* 3)如果greatest结点中的关键字个数不足则需要合并结点<br>
* <p>
* 首先查找B树中需删除的元素,如果该元素在B树中存在,则将该元素在其结点中进行删除,
* 如果删除该元素后,首先判断该元素是否有左右孩子结点,如果有,则上移孩子结点中的
* 某相近元素(“左孩子最右边的节点”或“右孩子最左边的节点”)到父节点中,然后是移动之
* 后的情况;如果没有,直接删除后,移动之后的情况。删除元素,移动相应元素之后,
* 如果某结点中元素数目(即关键字数)小于ceil(m/2)-1,则需要看其某相邻兄弟结点是
* 否丰满(结点中元素个数大于ceil(m/2)-1)(还记得第一节中关于B树的第5个特性中的
* c点么?: c)除根结点之外的结点(包括叶子结点)的关键字的个数n必须满足:
* (ceil(m / 2)-1)<= n <= m-1。m表示最多含有m个孩子,n表示关键字数。
*例如 在一颗5阶B树的示例中,关键字数n满足:2<=n<=4),如果丰满,则向父节点
* 借一个元素来满足条件;如果其相邻兄弟都刚脱贫,即借了之后其结点数目小于ceil(m/2)-1,
* 则该结点与其相邻的某一兄弟结点进行“合并”成一个结点,以此来满足条件。
* @param key Integer
* @return True if value was removed from the tree.
*/
public boolean removeKey(Integer key)
{
//先找到关键字在哪个结点中,如果没有找到此结点则直接返回false
BTNode node = this.findNode(key);
if(node == null)
return false;
int index = node.getKeys().indexOf(key);//获取此key的索引
node.getKeys().remove(key);//删除此key
if(node.sizeOfChildren() != 0)//非叶子结点
{
BTNode left = node.getChild(index);//左孩子
BTNode greatest = this.getGreatestNode(left);
//获取并删除左孩子最大结点中的最大的元素
Integer replaceValue = greatest.getKeys().remove(greatest.sizeOfKeys() - 1);
//将此元素添加到删除关键字key的结点中
node.addKey(replaceValue);
//如果删除关键字后导致下溢,则需要合并结点
if(greatest.sizeOfKeys() < Constants.MIN_KEY_SIZE)
{
this.combined(greatest); //greatest为叶子结点
}
}else{//叶子结点
//分为四种情况:
//1)既是叶子结点又是根节点且 node.sizeOfKeys() < Constants.MIN_KEY_SIZE
//2)叶子结点但非根结点且node.sizeOfKeys() < Constants.MIN_KEY_SIZE ----->只有这一种需要合并
//3)既是叶子结点又是根节点且node.sizeOfKeys() > Constants.MIN_KEY_SIZE
//4)叶子结点但非根结点且node.sizeOfKeys() > Constants.MIN_KEY_SIZE
if(node.getParentNode() != null && node.sizeOfKeys() < Constants.MIN_KEY_SIZE)
{
this.combined(node);
}else if(node.getParentNode() == null && node.sizeOfKeys() == 0)
{
//删除的是最后一个元素
root = null;
}
}
sizeOfKeys --;
return true;
}
/**
* 从上到下直到叶节点找到最大的叶结点
* @param node BTNode
* @return BTNode
*/
private BTNode getGreatestNode(BTNode node)
{
while(node.sizeOfChildren() != 0)
{
node = node.getChild(node.sizeOfChildren() - 1);
}
return node;
}
/**
* 此node结点中关键字个数不足,需要合并结点,与splitNode()方法类似,也是从叶节点开始向上递归的。<p>
* 需要分情况讨论: <br>
* 1) 此结点的右邻居存在且右邻居结点中关键字个数 >= minSize + 1 相当于左旋 ♣ <br>
* 2) 此结点的左邻居存在且左邻居结点中关键字个数 >= minSize + 1 相当于右旋 ♥<br>
* 3) 此结点的右邻居存在且父节点关键字个数大于0 结点合并 ♠<br>
* 4) 此结点的左邻居存在且父节点关键字个数大于0 结点合并 ♦<br>
* @param node BTNode
*/
private void combined(BTNode node)
{
//先获取此结点的父节点
BTNode parentNode = node.getParentNode();
//获取此结点是其父节点中的索引,即第几个孩子
int index = parentNode.getChildNodes().indexOf(node);
int indexOfLeftNeighbor = index - 1;
int indexOfRightNeighbor = index + 1;
BTNode rightNeighbor = null;
int rightNeighborSize = 0;//???
if(indexOfRightNeighbor < parentNode.sizeOfChildren())//右邻居存在
{
rightNeighbor = parentNode.getChild(indexOfRightNeighbor);
rightNeighborSize = rightNeighbor.sizeOfKeys();
}
//右邻居存在且其关键字个数大于最小值
if(rightNeighbor != null && rightNeighborSize > Constants.MIN_KEY_SIZE)
{
//相当于左旋
Integer removeValue = rightNeighbor.getKeys().remove(0);
int prev = getIndexOfPreviousValue(parentNode, removeValue);
Integer parentValue = parentNode.getKeys().remove(prev);
node.addKey(parentValue);
parentNode.addKey(removeValue);
if(rightNeighbor.sizeOfChildren() > 0)//如果右邻居的孩子结点存在,则需要把右邻居的第一个孩子结点删除并添加到node结点中
{
node.addChild(rightNeighbor.getChildNodes().remove(0));
}
}else{
BTNode leftNeighbor = null;
int leftNeighborSize = 0;//???
if(indexOfLeftNeighbor >= 0)//左邻居存在
{
leftNeighbor = parentNode.getChild(indexOfLeftNeighbor);
leftNeighborSize = leftNeighbor.sizeOfKeys();
}
//左邻居存在且其关键字个数大于最小值
if(leftNeighbor != null && leftNeighborSize > Constants.MIN_KEY_SIZE)//左邻居存在且其关键字个数大于最小值
{
//相当于右旋
Integer removeValue = leftNeighbor.getKeys().remove(leftNeighbor.sizeOfKeys() - 1);
int next = getIndexOfNextValue(parentNode, removeValue);
Integer parentValue = parentNode.getKeys().remove(next);
node.addKey(parentValue);
parentNode.addKey(removeValue);
if(leftNeighbor.sizeOfChildren() > 0)//如果左邻居的孩子结点存在,则需要把右邻居的最后一个孩子结点删除并添加到node结点中
{
node.addChild(leftNeighbor.getChildNodes().remove(leftNeighbor.sizeOfChildren() - 1));
}
}else if(rightNeighbor != null && parentNode.sizeOfKeys() > 0)//右邻居存在且父节点关键字个数大于0
{
Integer rightValue = rightNeighbor.getKey(0);//获取右邻居结点中最左边的关键字
int prev = getIndexOfPreviousValue(parentNode, rightValue);//获取rightValue关键字的父节点中不大于但最接近此关键字的索引
Integer parentKey = parentNode.getKeys().remove(prev);//在父节点中删除此索引对应的关键字
parentNode.removeChild(rightNeighbor); // 在父节点中删除此索引对应的孩子结点
node.addKey(parentKey);//将删除的关键字添加到关键字下溢的结点中
//将右邻居中的关键字添加进去
for(int i = 0 ; i < rightNeighbor.sizeOfKeys(); i ++)
{
node.addKey(rightNeighbor.getKey(i));
}
//将右邻居中的孩子结点也添加进去
for(int i = 0 ; i < rightNeighbor.sizeOfChildren(); i ++)
{
node.addChild(rightNeighbor.getChild(i));
}
if(parentNode.getParentNode() != null && parentNode.sizeOfKeys() < Constants.MIN_KEY_SIZE)//还没到达根节点
{
this.combined(parentNode);
}else if(parentNode.sizeOfKeys() == 0){
//父节点中没有关键字了,则降低树的高度
node.setParentNode(null);//注意:树的高度降低一层,此结点就变为根节点,一定要设置其父节点为null
parentNode = null;
root = node;
}
}else if(leftNeighbor != null && parentNode.sizeOfKeys() > 0) //左邻居存在且父节点关键字个数大于0
{
Integer leftValue = leftNeighbor.getKey(leftNeighbor.sizeOfKeys() - 1);//获取左邻居结点中最右边的关键字
int next = getIndexOfNextValue(parentNode, leftValue);//获取leftValue关键字的父节点中不小于但最接近此关键字的索引
Integer parentKey = parentNode.getKeys().remove(next);//在父节点中删除此索引对应的关键字
parentNode.removeChild(leftNeighbor); //在父节点中删除此索引对应的孩子结点
node.addKey(parentKey);//将删除的关键字添加到关键字下溢的结点中
//将左邻居中的关键字添加进去
for(int i = 0 ; i < leftNeighbor.sizeOfKeys(); i ++)
{
node.addKey(leftNeighbor.getKey(i));
}
//将左邻居中的孩子结点也添加进去
for(int i = 0 ; i < leftNeighbor.sizeOfChildren(); i ++)
{
node.addChild(leftNeighbor.getChild(i));
}
if(parentNode.getParentNode() != null && parentNode.sizeOfKeys() < Constants.MIN_KEY_SIZE)//还没到达根节点
{
this.combined(parentNode);
}else if(parentNode.sizeOfKeys() == 0){
//父节点中没有关键字了,则降低树的高度
node.setParentNode(null);//注意:树的高度降低一层,此结点就变为根节点,一定要设置其父节点为null
parentNode = null;
root = node;
}
}
}//end else
}
/**
* 返回node结点中值不大于但最接近与value的关键字的索引,都比value大则返回0,都比value小则返回size-1
* @param node BTNode
* @param value Integer
* @return int
*/
private int getIndexOfPreviousValue(BTNode node, Integer value)
{
for (int i = 1; i < node.sizeOfKeys(); i++)
{
Integer t = node.getKey(i);
if (t.compareTo(value) >= 0)
return i - 1;
}
return node.sizeOfKeys() - 1;
}
/**
* 返回node结点中值不小于但最接近与value的关键字的索引,都比value大则返回0,都比value小则返回size-1
* @param node BTNode
* @param value Integer
* @return
*/
private int getIndexOfNextValue(BTNode node, Integer value)
{
for(int i = 0 ; i < node.sizeOfKeys(); i ++)
{
Integer t = node.getKey(i);
if(t.compareTo(value) >= 0)
{
return i;
}
}
return node.sizeOfKeys() - 1;
}
/**
* 在BTree中查找key,若存在则返回此BTNode,不存在则返回null
* @param key Integer
* @return BTNode
*/
public BTNode findNode(Integer key)
{
BTNode node = root;
while(node != null)
{
if(node.sizeOfChildren() == 0)//叶子结点
{
if(node.getKeys().contains(key))
{
return node;
}
return null;
}else{//非叶子结点
if(key.compareTo(node.getKey(0)) < 0)//比最小的小
{
node = node.getChild(0);
continue;
}
if(key.compareTo(node.getKey(node.sizeOfKeys() - 1)) > 0)//比最大的还大
{
node = node.getChild(node.sizeOfKeys());
continue;
}
//中间情况
for(int i = 1 ;i < node.sizeOfKeys(); i ++)
{
if(key.compareTo(node.getKey(i)) == 0)
{
return node;
}else if(key.compareTo(node.getKey(i - 1)) > 0 && key.compareTo(node.getKey(i)) < 0)
{
node = node.getChild(i);
break;
}
}
}
}
return null;
}
/**
* @return BTree中关键字个数
*/
public int sizeOfKeys()
{
return sizeOfKeys;
}
/**
* 从叶子节点开始从下到上递归分裂
* @param node BTNode
* @param
*/
private void splitNode(BTNode node)
{
//分裂位置发生在size/2,分裂成两个结点
int splitIndex = node.sizeOfKeys() / 2;
//需要上移的关键字
Integer splitKey = node.getKeys().get(splitIndex);
// List<Integer> copy1 = new ArrayList<>(node.getKeys().subList(0, splitIndex));//---2
// List<Integer> copy2 = new ArrayList<>(node.getKeys().subList(splitIndex + 1, node.getKeys().size()));//----2
//生成新的结点
BTNode left = new BTNode(null);
// left.setKeys(node.getKeys().subList(0, splitIndex));
// left.setKeys(copy1); //-----2
for(int i = 0 ; i < splitIndex; i ++)
{
left.addKey(node.getKey(i));
}
if(node.sizeOfChildren() > 0)
{
left.addChildren(node.getChildNodes().subList(0, splitIndex + 1));//这里索引需要注意
}
BTNode right = new BTNode(null);
// right.setKeys(node.getKeys().subList(splitIndex + 1, node.getKeys().size()));
// right.setKeys(copy2); //----2
for(int i = splitIndex + 1; i < node.sizeOfKeys(); i ++)
{
right.addKey(node.getKey(i));
}
if(node.sizeOfChildren() > 0)
{
right.addChildren(node.getChildNodes().subList(splitIndex + 1, node.sizeOfChildren()));
}
if(node.getParentNode() != null)//有父节点
{
BTNode parent = node.getParentNode();//取得其父节点
parent.addKey(splitKey);
parent.removeChild(node);
parent.addChild(left);
parent.addChild(right);
if(parent.sizeOfKeys() > Constants.MAX_KEY_SIZE)
{
splitNode(parent);
}
}else{//没有父节点,即到达根节点
BTNode newRoot = new BTNode(null);
newRoot.getKeys().add(splitKey);
root = newRoot;
newRoot.addChild(left);
newRoot.addChild(right);
}
}
/**
* 插入关键字数组
* @param keys Integer[]
* @return boolean 是否插入成功
*/
public boolean insertKeys(Integer[] keys)
{
boolean isInsert ;
for(int i = 0 ; i < keys.length; i ++)
{
isInsert = insertKey(keys[i]);
if(! isInsert)
{
return false;
}
}
return true;
}
/**
* 插入关键字序列
* @param keys List
* @return boolean 是否插入成功
*/
public boolean insertKeys(List<Integer> keys)
{
Integer[] keysArray = (Integer[]) keys.toArray();
return insertKeys(keysArray);
}
private static class TreePrinter
{
public static String getString(BTree tree)
{
if (tree.root == null) return "Tree has no nodes.";
return getString(tree.root, "", true);
}
private static String getString(BTNode node, String prefix, boolean isTail)
{
StringBuilder builder = new StringBuilder();
builder.append(prefix).append((isTail ? "└── " : "├── "));
for (int i = 0; i < node.sizeOfKeys(); i++) {
Integer value = node.getKey(i);
builder.append(value);
if (i < node.sizeOfKeys() - 1) builder.append(", ");
}
builder.append("\n");
if (node.getChildNodes() != null) {
for (int i = 0; i < node.sizeOfChildren() - 1; i++) {
BTNode obj = node.getChild(i);
builder.append(getString(obj, prefix + (isTail ? " " : "│ "), false));
}
if (node.sizeOfChildren() >= 1) {
BTNode obj = node.getChild(node.sizeOfChildren() - 1);
builder.append(getString(obj, prefix + (isTail ? " " : "│ "), true));
}
}
return builder.toString();
}
}
public static void main(String[] args)
{
BTree bTree = BTree.newInstance();
for(int i = 1 ; i <= 10; i ++)
{
bTree.insertKey(i);
System.out.println(TreePrinter.getString(bTree));
}
for(int i = 10 ; i > 0; i --)
{
bTree.removeKey(i);
System.out.println(TreePrinter.getString(bTree));
}
}
}
B+树
背景
B+树是基于B-树的一种变体,有着比B-树更高效的查询性能。
定义
B+树具有如下特征(看不下去):
- 有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
- 所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
- 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
B+树的特点(看下去):
- 每个父节点的元素都出现在子节点中,是子节点的最大(或最小)元素。
- 卫星数据的位置。( 卫星数据,指的是索引元素所指向的数据记录,比如数据库中的某一行。在B-树中,无论中间节点还是叶子节点都带有卫星数据)
例子如下:
第一个特点
第二个特点
B-树中的卫星数据
B+树种的卫星数据
需要补充的是,在数据库的聚集索引(Clustered Index)中,叶子节点直接包含卫星数据。在非聚集索引(NonClustered Index)中,叶子节点带有指向卫星数据的指针。
查询对比
单行查询
在单元素查询的时候,B+树会自顶向下逐层查找节点,最终找到匹配的叶子节点。
找3
第一次磁盘IO
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nCMWG2fw-1607099252773)(C:\Users\Auraros\AppData\Roaming\Typora\typora-user-images\image-20201204214327310.png)\]](https://img-blog.csdnimg.cn/20201205003330254.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNjM0MDAx,size_16,color_FFFFFF,t_70)
第二次磁盘IO
第三次磁盘IO
两点不同:
1. B+树的中间节点没有卫星数据,所以同样大小的磁盘页可以容纳更多的节点元素。(意味着,数据量相同的情况下,B+树的结构比B-树更加‘矮胖’,因此查询次数更少)
2. B+树的查询必须是最终查找到叶子节点,B-树只要匹配到元素即可,因此B-树查找性能不稳定,B+树查找性能是稳定的
范围查找
查找3到11
B-树(中序遍历):
自顶向下,查找到范围的下限:
中序遍历到元素6
中序遍历到元素8:
中序遍历到元素9:
中序遍历到元素11,遍历结束:
B+树(链表上做遍历):
自顶向下,查找到范围的下限
通过链表指针,遍历到元素6, 8
通过链表指针,遍历到元素9, 11,遍历结束
B+树比B-树的优势:
- IO次数更少
- 查询性能更稳定
- 范围查询简便
总结
B+树的特征:
-
有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
-
所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。(链表)
-
所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
-
B+树查找时是从上到下查找;B-树则是从下往上查找(中序遍历)
代码
B+树节点定义
对比B-树多了指向前节点和后节点的节点指针
package src.Tree.BPlusTree;
import java.util.List;
/**
* @Author : Auraros
* @Description :
* @Data : 2020-12-04 22:14
* @Version : 1.0
*/
public class BPlusNode {
//节点的子节点
private List<BPlusNode> BPlusNodes;
//节点的键值对
private List<KeyAndValue> keyAndValue;
//节点的后节点
private BPlusNode nextBPlusNode;
//节点的前节点
private BPlusNode previousBPlusNode;
//节点的父节点
private BPlusNode parantBPlusNode;
public BPlusNode( List<BPlusNode> BPlusNodes, List<KeyAndValue> keyAndValue, BPlusNode nextBPlusNode,BPlusNode previousBPlusNode, BPlusNode parantBPlusNode) {
this.BPlusNodes = BPlusNodes;
this.keyAndValue = keyAndValue;
this.nextBPlusNode = nextBPlusNode;
this.parantBPlusNode = parantBPlusNode;
this.previousBPlusNode = previousBPlusNode;
}
boolean isLeaf() {
return BPlusNodes==null;
}
boolean isHead() {
return previousBPlusNode == null;
}
boolean isTail() {
return nextBPlusNode == null;
}
boolean isRoot() {
return parantBPlusNode == null;
}
List<BPlusNode> getBPlusNodes() {
return BPlusNodes;
}
void setBPlusNodes(List<BPlusNode> BPlusNodes) {
this.BPlusNodes = BPlusNodes;
}
List<KeyAndValue> getKeyAndValue() {
return keyAndValue;
}
// public void setKeyAndValue(List<KeyAndValue> KeyAndValue) {
// this.keyAndValue = KeyAndValue;
// }
BPlusNode getNextBPlusNode() {
return nextBPlusNode;
}
void setNextBPlusNode(BPlusNode nextBPlusNode) {
this.nextBPlusNode = nextBPlusNode;
}
BPlusNode getParantBPlusNode() {
return parantBPlusNode;
}
void setParantBPlusNode(BPlusNode parantBPlusNode) {
this.parantBPlusNode = parantBPlusNode;
}
BPlusNode getPreviousBPlusNode() {
return previousBPlusNode;
}
void setPreviousBPlusNode(BPlusNode previousBPlusNode) {
this.previousBPlusNode = previousBPlusNode;
}
}
还需要新建一个类来保存我们的关键字和信息(数据库所属行)
package src.Tree.BPlusTree;
/**
* @Author : Auraros
* @Description :
* @Data : 2020-12-04 22:16
* @Version : 1.0
*/
public class KeyAndValue implements Comparable<KeyAndValue>{
/*存储索引关键字*/
private int key;
/*存储数据*/
private Object value;
@Override
public int compareTo(KeyAndValue o) {
//根据key的值升序排列
return this.key - o.key;
}
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
KeyAndValue(int key, Object value) {
this.key = key;
this.value = value;
}
}
B+树创建定义
B+树插入实现
分裂操作很像我们的B—树,只是多个一个nextBPlusNode操作
1. 找到要插入的节点
2. 将关键字插入其中,然后判断是否需要分裂,需要的话就分裂
插入不分裂
此时插入17:
插入分裂
插入18:
当前结点的关键字个数大于5,进行分裂。分裂成两个结点,左结点2个记录,右结点3个记录,关键字16进位到父结点(索引类型)中,将当前结点的指针指向父结点。
代码如下:
public void insert(KeyAndValue entry) {
List<KeyAndValue> keyAndValues1 = new ArrayList<>();
//插入第一个节点
if (head == null) {
keyAndValues1.add(entry);
head = new BPlusNode(null, keyAndValues1, null, null, null);
root = new BPlusNode(null, keyAndValues1, null, null, null);
} else {
BPlusNode BPlusNode = head;
//遍历链表,找到插入键值对对应的节点
while (BPlusNode != null) {
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
int exitFlag = 0;
//如果插入的键的值和当前节点键值对集合中的某个键的值相等,则直接替换value
for (KeyAndValue KV : keyAndValues) {
if (KV.getKey() == entry.getKey()) {
KV.setValue(entry.getValue());
exitFlag = 1;
break;
}
}
//如果插入的键已经有了,则退出循环
if (exitFlag == 1) {
break;
}
//如果当前节点是最后一个节点或者要插入的键值对的键的值小于下一个节点的键的最小值,则直接插入当前节点
if (BPlusNode.getNextBPlusNode() == null || BPlusNode.getNextBPlusNode().getKeyAndValue().get(0).getKey() >= entry.getKey()) {
splidBPlusNode(BPlusNode, entry);
break;
}
//移动指针
BPlusNode = BPlusNode.getNextBPlusNode();
}
}
}
分裂操作代码:
//判断是否需要拆分节点
private void splidBPlusNode(BPlusNode BPlusNode, KeyAndValue addkeyAndValue) {
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
if (keyAndValues.size() == rank - 1) {
//先插入待添加的节点
keyAndValues.add(addkeyAndValue);
Collections.sort(keyAndValues);
//取出当前节点的键值对集合
//取出原来的key-value集合中间位置的下标
int mid = keyAndValues.size() / 2;
//取出原来的key-value集合中间位置的键
int midKey = keyAndValues.get(mid).getKey();
//构造一个新的键值对,不是叶子节点的节点不存储value的信息
KeyAndValue midKeyAndValue = new KeyAndValue(midKey, "");
//将中间位置左边的键值对封装成集合对象
List<KeyAndValue> leftKeyAndValues = new ArrayList<>();
for (int i = 0; i < mid; i++) {
leftKeyAndValues.add(keyAndValues.get(i));
}
//将中间位置右边边的键值对封装成集合对象
List<KeyAndValue> rightKeyAndValues = new ArrayList<>();
//如果是叶子节点则在原节点中保留上移的key-value,否则原节点删除上移的key-value
int k;
if (BPlusNode.isLeaf()) {
k = mid;
} else {
k = mid + 1;
}
for (int i = k; i < rank; i++) {
rightKeyAndValues.add(keyAndValues.get(i));
}
//对左右两边的元素重排序
Collections.sort(leftKeyAndValues);
Collections.sort(rightKeyAndValues);
//以mid为界限将当前节点分列成两个节点,维护前指针和后指针
BPlusNode rightBPlusNode;
BPlusNode leftBPlusNode;
// if (BPlusNode.isLeaf()) {
//如果是叶子节点维护前后指针
rightBPlusNode = new BPlusNode(null, rightKeyAndValues, BPlusNode.getNextBPlusNode(), null, BPlusNode.getParantBPlusNode());
leftBPlusNode = new BPlusNode(null, leftKeyAndValues, rightBPlusNode, BPlusNode.getPreviousBPlusNode(), BPlusNode.getParantBPlusNode());
rightBPlusNode.setPreviousBPlusNode(leftBPlusNode);
// } else {
// //如果不是叶子不维护前后指针
// rightBPlusNode = new BPlusNode(null, rightKeyAndValues, null, null, BPlusNode.getParantBPlusNode());
// leftBPlusNode = new BPlusNode(null, leftKeyAndValues, null, null, BPlusNode.getParantBPlusNode());
// }
//如果当前分裂的节点有孩子节点,设置分裂后节点和孩子节点的关系
if (BPlusNode.getBPlusNodes() != null) {
//取得所有地孩子节点
List<BPlusNode> BPlusNodes = BPlusNode.getBPlusNodes();
List<BPlusNode> leftBPlusNodes = new ArrayList<>();
List<BPlusNode> rightBPlusNodes = new ArrayList<>();
for (BPlusNode childBPlusNode : BPlusNodes) {
//取得当前孩子节点的最大键值
int max = childBPlusNode.getKeyAndValue().get(childBPlusNode.getKeyAndValue().size() - 1).getKey();
if (max < midKeyAndValue.getKey()) {
//小于mid处的键的数是左节点的子节点
leftBPlusNodes.add(childBPlusNode);
childBPlusNode.setParantBPlusNode(leftBPlusNode);
} else {
//大于mid处的键的数是右节点的子节点
rightBPlusNodes.add(childBPlusNode);
childBPlusNode.setParantBPlusNode(rightBPlusNode);
}
}
leftBPlusNode.setBPlusNodes(leftBPlusNodes);
rightBPlusNode.setBPlusNodes(rightBPlusNodes);
}
//当前节点的前节点
BPlusNode preBPlusNode = BPlusNode.getPreviousBPlusNode();
//分裂节点后将分裂节点的前节点的后节点设置为左节点
if (preBPlusNode != null) {
preBPlusNode.setNextBPlusNode(leftBPlusNode);
}
//当前节点的后节点
BPlusNode nextBPlusNode = BPlusNode.getNextBPlusNode();
//分裂节点后将分裂节点的后节点的前节点设置为右节点
if (nextBPlusNode != null) {
nextBPlusNode.setPreviousBPlusNode(rightBPlusNode);
}
//如果由头结点分裂,则分裂后左边的节点为头节点
if (BPlusNode == head) {
head = leftBPlusNode;
}
//父节点的子节点
List<BPlusNode> childBPlusNodes = new ArrayList<>();
childBPlusNodes.add(rightBPlusNode);
childBPlusNodes.add(leftBPlusNode);
//分裂
//当前节点无父节点
if (BPlusNode.getParantBPlusNode() == null) {
//父节点的键值对
List<KeyAndValue> parentKeyAndValues = new ArrayList<>();
parentKeyAndValues.add(midKeyAndValue);
//构造父节点
BPlusNode parentBPlusNode = new BPlusNode(childBPlusNodes, parentKeyAndValues, null, null, null);
//将子节点与父节点关联
rightBPlusNode.setParantBPlusNode(parentBPlusNode);
leftBPlusNode.setParantBPlusNode(parentBPlusNode);
//当前节点为根节点
root = parentBPlusNode;
} else {
BPlusNode parentBPlusNode = BPlusNode.getParantBPlusNode();
//将原来的孩子节点(除了被拆分的节点)和新的孩子节点(左孩子和右孩子)合并之后与父节点关联
childBPlusNodes.addAll(parentBPlusNode.getBPlusNodes());
//移除正在被拆分的节点
childBPlusNodes.remove(BPlusNode);
//将子节点与父节点关联
parentBPlusNode.setBPlusNodes(childBPlusNodes);
rightBPlusNode.setParantBPlusNode(parentBPlusNode);
leftBPlusNode.setParantBPlusNode(parentBPlusNode);
if (parentBPlusNode.getParantBPlusNode() == null) {
root = parentBPlusNode;
}
//当前节点有父节点,递归调用拆分的方法,将父节点拆分
splidBPlusNode(parentBPlusNode, midKeyAndValue);
}
} else {
keyAndValues.add(addkeyAndValue);
//排序
Collections.sort(keyAndValues);
}
}
B+树删除操作实现
如果叶子结点中没有相应的key,则删除失败。否则执行下面的步骤
1.删除叶子结点中对应的key。删除后若结点的key的个数大于等于Math.ceil(m-1)/2 – 1,删除操作结束,否则执行第2步。
2.若兄弟结点key有富余(大于Math.ceil(m-1)/2 – 1),向兄弟结点借一个记录,同时用借到的key替换父结(指当前结点和兄弟结点共同的父结点)点中的key,删除结束。否则执行第3步。
3.若兄弟结点中没有富余的key,则当前结点和兄弟结点合并成一个新的叶子结点,并删除父结点中的key(父结点中的这个key两边的孩子指针就变成了一个指针,正好指向这个新的叶子结点),将当前结点指向父结点(必为索引结点),执行第4步(第4步以后的操作和B树就完全一样了,主要是为了更新索引结点)。
4.若索引结点的key的个数大于等于Math.ceil(m-1)/2 – 1,则删除操作结束。否则执行第5步
5.若兄弟结点有富余,父结点key下移,兄弟结点key上移,删除结束。否则执行第6步
6.当前结点和兄弟结点及父结点下移key合并成一个新的结点。将当前结点指向父结点,重复第4步。
注意,通过B+树的删除操作后,索引结点中存在的key,不一定在叶子结点中存在对应的记录。
下面是一颗5阶B树的删除过程,5阶B数的结点最少2个key,最多4个key。
删除后不操作的情况
删除22,删除后结果如下图
删除后操作
删除15
删除后当前结点只有一个key,不满足条件,而兄弟结点有三个key,可以从兄弟结点借一个关键字为9的记录,同时更新将父结点中的关键字由10也变为9,删除结束。
删除合并操作
删除7,删除后的结果如下图所示
当前结点关键字个数小于2,(左)兄弟结点中的也没有富余的关键字(当前结点还有个右兄弟,不过选择任意一个进行分析就可以了,这里我们选择了左边的),所以当前结点和兄弟结点合并,并删除父结点中的key,当前结点指向父结点。
此时当前结点的关键字个数小于2,兄弟结点的关键字也没有富余,所以父结点中的关键字下移,和两个孩子结点合并,结果如下图所示。
删除代码:
public boolean delete(int key) {
System.out.println("delete:" + key);
System.out.println();
//首先找到要删除的key所在的节点
BPlusNode deleteBPlusNode = (BPlusNode) search(key, root, NODE);
//如果没找到则删除失败
if (deleteBPlusNode == null) {
return false;
}
if (deleteBPlusNode == root) {
delKeyAndValue(root.getKeyAndValue(), key);
return true;
}
if (deleteBPlusNode == head && isNeedMerge(head)) {
head = head.getNextBPlusNode();
}
return merge(deleteBPlusNode, key);
}
合并操作:
//合并节点
//key 待删除的key
private boolean mergeBPlusNode(BPlusNode BPlusNode, int key) {
if (BPlusNode.isRoot()) {
return false;
}
BPlusNode preBPlusNode;
BPlusNode nextBPlusNode;
BPlusNode parentBPlusNode = BPlusNode.getParantBPlusNode();
List<BPlusNode> childBPlusNodes = parentBPlusNode.getBPlusNodes();
List<BPlusNode> childBPlusNodes1 = BPlusNode.getBPlusNodes();
List<KeyAndValue> parentKeyAndValue = parentBPlusNode.getKeyAndValue();
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
if (BPlusNode.isLeaf()) {
if (parentKeyAndValue.size() == 1 && parentBPlusNode != root) {
return true;
}
preBPlusNode = getPreviousBPlusNode(BPlusNode);
nextBPlusNode = getNextBPlusNode(BPlusNode);
if (preBPlusNode != null) {
List<KeyAndValue> preKeyAndValues = preBPlusNode.getKeyAndValue();
keyAndValues.addAll(preKeyAndValues);
if (preBPlusNode.isHead()) {
head = BPlusNode;
BPlusNode.setPreviousBPlusNode(null);
} else {
preBPlusNode.getPreviousBPlusNode().setNextBPlusNode(BPlusNode);
BPlusNode.setPreviousBPlusNode(preBPlusNode.getPreviousBPlusNode());
}
//将合并后节点的后节点设置为当前节点的后节点
preBPlusNode.setNextBPlusNode(BPlusNode.getNextBPlusNode());
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentBPlusNode, getMinKeyInBPlusNode(preBPlusNode), key);
delKeyAndValue(parentKeyAndValue, keyAndValue.getKey());
if (parentKeyAndValue.isEmpty()) {
root = BPlusNode;
} else {
//删除当前节点
childBPlusNodes.remove(preBPlusNode);
}
Collections.sort(keyAndValues);
merge(parentBPlusNode, key);
return true;
}
if (nextBPlusNode != null) {
List<KeyAndValue> nextKeyAndValues = nextBPlusNode.getKeyAndValue();
keyAndValues.addAll(nextKeyAndValues);
if (nextBPlusNode.isTail()) {
BPlusNode.setPreviousBPlusNode(null);
} else {
nextBPlusNode.getNextBPlusNode().setPreviousBPlusNode(BPlusNode);
BPlusNode.setNextBPlusNode(nextBPlusNode.getNextBPlusNode());
}
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentBPlusNode, key, getMinKeyInBPlusNode(nextBPlusNode));
delKeyAndValue(parentKeyAndValue, keyAndValue.getKey());
if (parentKeyAndValue.isEmpty()) {
root = BPlusNode;
BPlusNode.setParantBPlusNode(null);
} else {
//删除当前节点
childBPlusNodes.remove(nextBPlusNode);
}
Collections.sort(keyAndValues);
merge(parentBPlusNode, key);
return true;
}
//前节点和后节点都等于null那么是root节点
return false;
} else {
preBPlusNode = getPreviousBPlusNode(BPlusNode);
nextBPlusNode = getNextBPlusNode(BPlusNode);
if (preBPlusNode != null) {
//将前一个节点和当前节点还有父节点中的相应Key-value合并
List<KeyAndValue> preKeyAndValues = preBPlusNode.getKeyAndValue();
preKeyAndValues.addAll(keyAndValues);
int min = getMaxKeyInBPlusNode(preBPlusNode);
int max = getMinKeyInBPlusNode(BPlusNode);
//父节点中移除这个key-value
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentBPlusNode, min, max);
parentKeyAndValue.remove(keyAndValue);
if (parentKeyAndValue.isEmpty()) {
root = preBPlusNode;
BPlusNode.setParantBPlusNode(null);
preBPlusNode.setParantBPlusNode(null);
} else {
childBPlusNodes.remove(BPlusNode);
}
assert nextBPlusNode != null;
preBPlusNode.setNextBPlusNode(nextBPlusNode.getNextBPlusNode());
//前节点加上一个当前节点的所有子节点中最小key的key-value
KeyAndValue minKeyAndValue = getMinKeyAndValueInChildBPlusNode(BPlusNode);
assert minKeyAndValue != null;
KeyAndValue keyAndValue1 = new KeyAndValue(minKeyAndValue.getKey(), minKeyAndValue.getValue());
preKeyAndValues.add(keyAndValue1);
List<BPlusNode> preChildBPlusNodes = preBPlusNode.getBPlusNodes();
preChildBPlusNodes.addAll(BPlusNode.getBPlusNodes());
//将当前节点的孩子节点的父节点设为当前节点的后节点
for (BPlusNode BPlusNode1 : childBPlusNodes1) {
BPlusNode1.setParantBPlusNode(preBPlusNode);
}
Collections.sort(preKeyAndValues);
merge(parentBPlusNode, key);
return true;
}
if (nextBPlusNode != null) {
//将后一个节点和当前节点还有父节点中的相应Key-value合并
List<KeyAndValue> nextKeyAndValues = nextBPlusNode.getKeyAndValue();
nextKeyAndValues.addAll(keyAndValues);
int min = getMaxKeyInBPlusNode(BPlusNode);
int max = getMinKeyInBPlusNode(nextBPlusNode);
//父节点中移除这个key-value
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentBPlusNode, min, max);
parentKeyAndValue.remove(keyAndValue);
childBPlusNodes.remove(BPlusNode);
if (parentKeyAndValue.isEmpty()) {
root = nextBPlusNode;
nextBPlusNode.setParantBPlusNode(null);
} else {
childBPlusNodes.remove(BPlusNode);
}
nextBPlusNode.setPreviousBPlusNode(BPlusNode.getPreviousBPlusNode());
//后节点加上一个当后节点的所有子节点中最小key的key-value
KeyAndValue minKeyAndValue = getMinKeyAndValueInChildBPlusNode(nextBPlusNode);
assert minKeyAndValue != null;
KeyAndValue keyAndValue1 = new KeyAndValue(minKeyAndValue.getKey(), minKeyAndValue.getValue());
nextKeyAndValues.add(keyAndValue1);
List<BPlusNode> nextChildBPlusNodes = nextBPlusNode.getBPlusNodes();
nextChildBPlusNodes.addAll(BPlusNode.getBPlusNodes());
//将当前节点的孩子节点的父节点设为当前节点的后节点
for (BPlusNode BPlusNode1 : childBPlusNodes1) {
BPlusNode1.setParantBPlusNode(nextBPlusNode);
}
Collections.sort(nextKeyAndValues);
merge(parentBPlusNode, key);
return true;
}
return false;
}
}
全部建树代码如下:
package src.Tree.BPlusTree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @Author : Auraros
* @Description :
* @Data : 2020-12-04 22:23
* @Version : 1.0
*/
public class BPlusTree {
private static final String NODE = " NODE";
static final String INT = "INT";
private static final String PREBPlusNode = "PREBPlusNode";
private static final String NEXTBPlusNode = "NEXTBPlusNode";
//B+树的阶数
private int rank;
//根节点
private BPlusNode root;
//头结点
private BPlusNode head;
BPlusTree(int rank) {
this.rank = rank;
}
public BPlusNode getRoot() {
return root;
}
public void insert(KeyAndValue entry) {
List<KeyAndValue> keyAndValues1 = new ArrayList<>();
//插入第一个节点
if (head == null) {
keyAndValues1.add(entry);
head = new BPlusNode(null, keyAndValues1, null, null, null);
root = new BPlusNode(null, keyAndValues1, null, null, null);
} else {
BPlusNode BPlusNode = head;
//遍历链表,找到插入键值对对应的节点
while (BPlusNode != null) {
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
int exitFlag = 0;
//如果插入的键的值和当前节点键值对集合中的某个键的值相等,则直接替换value
for (KeyAndValue KV : keyAndValues) {
if (KV.getKey() == entry.getKey()) {
KV.setValue(entry.getValue());
exitFlag = 1;
break;
}
}
//如果插入的键已经有了,则退出循环
if (exitFlag == 1) {
break;
}
//如果当前节点是最后一个节点或者要插入的键值对的键的值小于下一个节点的键的最小值,则直接插入当前节点
if (BPlusNode.getNextBPlusNode() == null || BPlusNode.getNextBPlusNode().getKeyAndValue().get(0).getKey() >= entry.getKey()) {
splidBPlusNode(BPlusNode, entry);
break;
}
//移动指针
BPlusNode = BPlusNode.getNextBPlusNode();
}
}
}
//判断是否需要拆分节点
private void splidBPlusNode(BPlusNode BPlusNode, KeyAndValue addkeyAndValue) {
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
if (keyAndValues.size() == rank - 1) {
//先插入待添加的节点
keyAndValues.add(addkeyAndValue);
Collections.sort(keyAndValues);
//取出当前节点的键值对集合
//取出原来的key-value集合中间位置的下标
int mid = keyAndValues.size() / 2;
//取出原来的key-value集合中间位置的键
int midKey = keyAndValues.get(mid).getKey();
//构造一个新的键值对,不是叶子节点的节点不存储value的信息
KeyAndValue midKeyAndValue = new KeyAndValue(midKey, "");
//将中间位置左边的键值对封装成集合对象
List<KeyAndValue> leftKeyAndValues = new ArrayList<>();
for (int i = 0; i < mid; i++) {
leftKeyAndValues.add(keyAndValues.get(i));
}
//将中间位置右边边的键值对封装成集合对象
List<KeyAndValue> rightKeyAndValues = new ArrayList<>();
//如果是叶子节点则在原节点中保留上移的key-value,否则原节点删除上移的key-value
int k;
if (BPlusNode.isLeaf()) {
k = mid;
} else {
k = mid + 1;
}
for (int i = k; i < rank; i++) {
rightKeyAndValues.add(keyAndValues.get(i));
}
//对左右两边的元素重排序
Collections.sort(leftKeyAndValues);
Collections.sort(rightKeyAndValues);
//以mid为界限将当前节点分列成两个节点,维护前指针和后指针
BPlusNode rightBPlusNode;
BPlusNode leftBPlusNode;
// if (BPlusNode.isLeaf()) {
//如果是叶子节点维护前后指针
rightBPlusNode = new BPlusNode(null, rightKeyAndValues, BPlusNode.getNextBPlusNode(), null, BPlusNode.getParantBPlusNode());
leftBPlusNode = new BPlusNode(null, leftKeyAndValues, rightBPlusNode, BPlusNode.getPreviousBPlusNode(), BPlusNode.getParantBPlusNode());
rightBPlusNode.setPreviousBPlusNode(leftBPlusNode);
// } else {
// //如果不是叶子不维护前后指针
// rightBPlusNode = new BPlusNode(null, rightKeyAndValues, null, null, BPlusNode.getParantBPlusNode());
// leftBPlusNode = new BPlusNode(null, leftKeyAndValues, null, null, BPlusNode.getParantBPlusNode());
// }
//如果当前分裂的节点有孩子节点,设置分裂后节点和孩子节点的关系
if (BPlusNode.getBPlusNodes() != null) {
//取得所有地孩子节点
List<BPlusNode> BPlusNodes = BPlusNode.getBPlusNodes();
List<BPlusNode> leftBPlusNodes = new ArrayList<>();
List<BPlusNode> rightBPlusNodes = new ArrayList<>();
for (BPlusNode childBPlusNode : BPlusNodes) {
//取得当前孩子节点的最大键值
int max = childBPlusNode.getKeyAndValue().get(childBPlusNode.getKeyAndValue().size() - 1).getKey();
if (max < midKeyAndValue.getKey()) {
//小于mid处的键的数是左节点的子节点
leftBPlusNodes.add(childBPlusNode);
childBPlusNode.setParantBPlusNode(leftBPlusNode);
} else {
//大于mid处的键的数是右节点的子节点
rightBPlusNodes.add(childBPlusNode);
childBPlusNode.setParantBPlusNode(rightBPlusNode);
}
}
leftBPlusNode.setBPlusNodes(leftBPlusNodes);
rightBPlusNode.setBPlusNodes(rightBPlusNodes);
}
//当前节点的前节点
BPlusNode preBPlusNode = BPlusNode.getPreviousBPlusNode();
//分裂节点后将分裂节点的前节点的后节点设置为左节点
if (preBPlusNode != null) {
preBPlusNode.setNextBPlusNode(leftBPlusNode);
}
//当前节点的后节点
BPlusNode nextBPlusNode = BPlusNode.getNextBPlusNode();
//分裂节点后将分裂节点的后节点的前节点设置为右节点
if (nextBPlusNode != null) {
nextBPlusNode.setPreviousBPlusNode(rightBPlusNode);
}
//如果由头结点分裂,则分裂后左边的节点为头节点
if (BPlusNode == head) {
head = leftBPlusNode;
}
//父节点的子节点
List<BPlusNode> childBPlusNodes = new ArrayList<>();
childBPlusNodes.add(rightBPlusNode);
childBPlusNodes.add(leftBPlusNode);
//分裂
//当前节点无父节点
if (BPlusNode.getParantBPlusNode() == null) {
//父节点的键值对
List<KeyAndValue> parentKeyAndValues = new ArrayList<>();
parentKeyAndValues.add(midKeyAndValue);
//构造父节点
BPlusNode parentBPlusNode = new BPlusNode(childBPlusNodes, parentKeyAndValues, null, null, null);
//将子节点与父节点关联
rightBPlusNode.setParantBPlusNode(parentBPlusNode);
leftBPlusNode.setParantBPlusNode(parentBPlusNode);
//当前节点为根节点
root = parentBPlusNode;
} else {
BPlusNode parentBPlusNode = BPlusNode.getParantBPlusNode();
//将原来的孩子节点(除了被拆分的节点)和新的孩子节点(左孩子和右孩子)合并之后与父节点关联
childBPlusNodes.addAll(parentBPlusNode.getBPlusNodes());
//移除正在被拆分的节点
childBPlusNodes.remove(BPlusNode);
//将子节点与父节点关联
parentBPlusNode.setBPlusNodes(childBPlusNodes);
rightBPlusNode.setParantBPlusNode(parentBPlusNode);
leftBPlusNode.setParantBPlusNode(parentBPlusNode);
if (parentBPlusNode.getParantBPlusNode() == null) {
root = parentBPlusNode;
}
//当前节点有父节点,递归调用拆分的方法,将父节点拆分
splidBPlusNode(parentBPlusNode, midKeyAndValue);
}
} else {
keyAndValues.add(addkeyAndValue);
//排序
Collections.sort(keyAndValues);
}
}
//打印B+树
void printBPlusTree(BPlusNode root) {
if (root == this.root) {
//打印根节点内的元素
printBPlusNode(root);
System.out.println();
}
if (root == null) {
return;
}
//打印子节点的元素
if (root.getBPlusNodes() != null) {
//找到最左边的节点
BPlusNode leftBPlusNode = null;
BPlusNode tmpBPlusNode = null;
List<BPlusNode> childBPlusNodes = root.getBPlusNodes();
for (BPlusNode BPlusNode : childBPlusNodes) {
if (BPlusNode.getPreviousBPlusNode() == null) {
leftBPlusNode = BPlusNode;
tmpBPlusNode = BPlusNode;
}
}
while (leftBPlusNode != null) {
//从最左边的节点向右打印
printBPlusNode(leftBPlusNode);
System.out.print("|");
leftBPlusNode = leftBPlusNode.getNextBPlusNode();
}
System.out.println();
printBPlusTree(tmpBPlusNode);
}
}
//打印一个节点内的元素
private void printBPlusNode(BPlusNode BPlusNode) {
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
for (int i = 0; i < keyAndValues.size(); i++) {
if (i != (keyAndValues.size() - 1)) {
System.out.print(keyAndValues.get(i).getKey() + ",");
} else {
System.out.print(keyAndValues.get(i).getKey());
}
}
}
public Object search(int key, BPlusNode BPlusNode, String mode) {
//如果是叶子节点则直接取值
if (BPlusNode.isLeaf()) {
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
for (KeyAndValue keyAndValue : keyAndValues) {
if (keyAndValue.getKey() == key) {
switch (mode) {
case NODE:
return BPlusNode;
case INT:
return keyAndValue.getValue();
}
}
}
return null;
}
List<BPlusNode> BPlusNodes = BPlusNode.getBPlusNodes();
//如果寻找的key小于节点的键的最小值
int minKey = BPlusNode.getKeyAndValue().get(0).getKey();
if (key < minKey) {
for (BPlusNode n : BPlusNodes) {
List<KeyAndValue> keyAndValues = n.getKeyAndValue();
//找到子节点集合中最大键小于父节点最小键节点
if (keyAndValues.get(keyAndValues.size() - 1).getKey() < minKey) {
return search(key, n, mode);
}
}
}
//如果寻找的key大于节点的键的最大值
int maxKey = getMaxKeyInBPlusNode(BPlusNode);
if (key >= maxKey) {
for (BPlusNode n : BPlusNodes) {
List<KeyAndValue> keyAndValues = n.getKeyAndValue();
//找到子节点集合中最小键大于等于父节点最小大键节点
if (keyAndValues.get(0).getKey() >= maxKey) {
return search(key, n, mode);
}
}
}
//如果寻找的key在最大值和最小值之间,首先定位到最窄的区间
int min = getLeftBoundOfKey(BPlusNode, key);
int max = getRightBoundOfKey(BPlusNode, key);
//去所有的子节点中找键的范围在min和max之间的节点
for (BPlusNode n : BPlusNodes) {
List<KeyAndValue> kvs = n.getKeyAndValue();
//找到子节点集合中键的范围在min和max之间的节点
if (kvs.get(0).getKey() >= min && kvs.get(kvs.size() - 1).getKey() < max) {
return search(key, n, mode);
}
}
return null;
}
public boolean delete(int key) {
System.out.println("delete:" + key);
System.out.println();
//首先找到要删除的key所在的节点
BPlusNode deleteBPlusNode = (BPlusNode) search(key, root, NODE);
//如果没找到则删除失败
if (deleteBPlusNode == null) {
return false;
}
if (deleteBPlusNode == root) {
delKeyAndValue(root.getKeyAndValue(), key);
return true;
}
if (deleteBPlusNode == head && isNeedMerge(head)) {
head = head.getNextBPlusNode();
}
return merge(deleteBPlusNode, key);
}
//平衡当前节点和前节点或者后节点的数量,使两者的数量都满足条件
private boolean balanceBPlusNode(BPlusNode BPlusNode, BPlusNode bratherBPlusNode, String BPlusNodeType) {
if (bratherBPlusNode == null) {
return false;
}
List<KeyAndValue> delKeyAndValues = BPlusNode.getKeyAndValue();
if (isMoreElement(bratherBPlusNode)) {
List<KeyAndValue> bratherKeyAndValues = bratherBPlusNode.getKeyAndValue();
int bratherSize = bratherKeyAndValues.size();
//兄弟节点删除挪走的键值对
KeyAndValue keyAndValue = null;
KeyAndValue keyAndValue1;
switch (BPlusNodeType) {
case PREBPlusNode:
keyAndValue = bratherKeyAndValues.remove(bratherSize - 1);
keyAndValue1 = getKeyAndValueinMinAndMax(BPlusNode.getParantBPlusNode(), keyAndValue.getKey(), getMinKeyInBPlusNode(BPlusNode));
keyAndValue1.setKey(keyAndValue.getKey());
break;
case NEXTBPlusNode:
keyAndValue = bratherKeyAndValues.remove(0);
keyAndValue1 = getKeyAndValueinMinAndMax(BPlusNode.getParantBPlusNode(), getMaxKeyInBPlusNode(BPlusNode), keyAndValue.getKey());
keyAndValue1.setKey(bratherKeyAndValues.get(0).getKey());
break;
}
//当前节点添加从前一个节点得来的键值对
delKeyAndValues.add(keyAndValue);
//对键值对重排序
Collections.sort(delKeyAndValues);
return true;
}
return false;
}
public boolean merge(BPlusNode BPlusNode, int key) {
List<KeyAndValue> delKeyAndValues = BPlusNode.getKeyAndValue();
//首先删除该key-vaule
delKeyAndValue(delKeyAndValues, key);
//如果要删除的节点的键值对的数目小于节点最大键值对数目*填充因子
if (isNeedMerge(BPlusNode)) {
Boolean isBalance;
//如果左节点有富余的键值对,则取一个到当前节点
BPlusNode preBPlusNode = getPreviousBPlusNode(BPlusNode);
isBalance = balanceBPlusNode(BPlusNode, preBPlusNode, PREBPlusNode);
//如果此时已经平衡,则已经删除成功
if (isBalance) return true;
//如果右兄弟节点有富余的键值对,则取一个到当前节点
BPlusNode nextBPlusNode = getNextBPlusNode(BPlusNode);
isBalance = balanceBPlusNode(BPlusNode, nextBPlusNode, NEXTBPlusNode);
return isBalance || mergeBPlusNode(BPlusNode, key);
} else {
return true;
}
}
//合并节点
//key 待删除的key
private boolean mergeBPlusNode(BPlusNode BPlusNode, int key) {
if (BPlusNode.isRoot()) {
return false;
}
BPlusNode preBPlusNode;
BPlusNode nextBPlusNode;
BPlusNode parentBPlusNode = BPlusNode.getParantBPlusNode();
List<BPlusNode> childBPlusNodes = parentBPlusNode.getBPlusNodes();
List<BPlusNode> childBPlusNodes1 = BPlusNode.getBPlusNodes();
List<KeyAndValue> parentKeyAndValue = parentBPlusNode.getKeyAndValue();
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
if (BPlusNode.isLeaf()) {
if (parentKeyAndValue.size() == 1 && parentBPlusNode != root) {
return true;
}
preBPlusNode = getPreviousBPlusNode(BPlusNode);
nextBPlusNode = getNextBPlusNode(BPlusNode);
if (preBPlusNode != null) {
List<KeyAndValue> preKeyAndValues = preBPlusNode.getKeyAndValue();
keyAndValues.addAll(preKeyAndValues);
if (preBPlusNode.isHead()) {
head = BPlusNode;
BPlusNode.setPreviousBPlusNode(null);
} else {
preBPlusNode.getPreviousBPlusNode().setNextBPlusNode(BPlusNode);
BPlusNode.setPreviousBPlusNode(preBPlusNode.getPreviousBPlusNode());
}
//将合并后节点的后节点设置为当前节点的后节点
preBPlusNode.setNextBPlusNode(BPlusNode.getNextBPlusNode());
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentBPlusNode, getMinKeyInBPlusNode(preBPlusNode), key);
delKeyAndValue(parentKeyAndValue, keyAndValue.getKey());
if (parentKeyAndValue.isEmpty()) {
root = BPlusNode;
} else {
//删除当前节点
childBPlusNodes.remove(preBPlusNode);
}
Collections.sort(keyAndValues);
merge(parentBPlusNode, key);
return true;
}
if (nextBPlusNode != null) {
List<KeyAndValue> nextKeyAndValues = nextBPlusNode.getKeyAndValue();
keyAndValues.addAll(nextKeyAndValues);
if (nextBPlusNode.isTail()) {
BPlusNode.setPreviousBPlusNode(null);
} else {
nextBPlusNode.getNextBPlusNode().setPreviousBPlusNode(BPlusNode);
BPlusNode.setNextBPlusNode(nextBPlusNode.getNextBPlusNode());
}
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentBPlusNode, key, getMinKeyInBPlusNode(nextBPlusNode));
delKeyAndValue(parentKeyAndValue, keyAndValue.getKey());
if (parentKeyAndValue.isEmpty()) {
root = BPlusNode;
BPlusNode.setParantBPlusNode(null);
} else {
//删除当前节点
childBPlusNodes.remove(nextBPlusNode);
}
Collections.sort(keyAndValues);
merge(parentBPlusNode, key);
return true;
}
//前节点和后节点都等于null那么是root节点
return false;
} else {
preBPlusNode = getPreviousBPlusNode(BPlusNode);
nextBPlusNode = getNextBPlusNode(BPlusNode);
if (preBPlusNode != null) {
//将前一个节点和当前节点还有父节点中的相应Key-value合并
List<KeyAndValue> preKeyAndValues = preBPlusNode.getKeyAndValue();
preKeyAndValues.addAll(keyAndValues);
int min = getMaxKeyInBPlusNode(preBPlusNode);
int max = getMinKeyInBPlusNode(BPlusNode);
//父节点中移除这个key-value
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentBPlusNode, min, max);
parentKeyAndValue.remove(keyAndValue);
if (parentKeyAndValue.isEmpty()) {
root = preBPlusNode;
BPlusNode.setParantBPlusNode(null);
preBPlusNode.setParantBPlusNode(null);
} else {
childBPlusNodes.remove(BPlusNode);
}
assert nextBPlusNode != null;
preBPlusNode.setNextBPlusNode(nextBPlusNode.getNextBPlusNode());
//前节点加上一个当前节点的所有子节点中最小key的key-value
KeyAndValue minKeyAndValue = getMinKeyAndValueInChildBPlusNode(BPlusNode);
assert minKeyAndValue != null;
KeyAndValue keyAndValue1 = new KeyAndValue(minKeyAndValue.getKey(), minKeyAndValue.getValue());
preKeyAndValues.add(keyAndValue1);
List<BPlusNode> preChildBPlusNodes = preBPlusNode.getBPlusNodes();
preChildBPlusNodes.addAll(BPlusNode.getBPlusNodes());
//将当前节点的孩子节点的父节点设为当前节点的后节点
for (BPlusNode BPlusNode1 : childBPlusNodes1) {
BPlusNode1.setParantBPlusNode(preBPlusNode);
}
Collections.sort(preKeyAndValues);
merge(parentBPlusNode, key);
return true;
}
if (nextBPlusNode != null) {
//将后一个节点和当前节点还有父节点中的相应Key-value合并
List<KeyAndValue> nextKeyAndValues = nextBPlusNode.getKeyAndValue();
nextKeyAndValues.addAll(keyAndValues);
int min = getMaxKeyInBPlusNode(BPlusNode);
int max = getMinKeyInBPlusNode(nextBPlusNode);
//父节点中移除这个key-value
KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentBPlusNode, min, max);
parentKeyAndValue.remove(keyAndValue);
childBPlusNodes.remove(BPlusNode);
if (parentKeyAndValue.isEmpty()) {
root = nextBPlusNode;
nextBPlusNode.setParantBPlusNode(null);
} else {
childBPlusNodes.remove(BPlusNode);
}
nextBPlusNode.setPreviousBPlusNode(BPlusNode.getPreviousBPlusNode());
//后节点加上一个当后节点的所有子节点中最小key的key-value
KeyAndValue minKeyAndValue = getMinKeyAndValueInChildBPlusNode(nextBPlusNode);
assert minKeyAndValue != null;
KeyAndValue keyAndValue1 = new KeyAndValue(minKeyAndValue.getKey(), minKeyAndValue.getValue());
nextKeyAndValues.add(keyAndValue1);
List<BPlusNode> nextChildBPlusNodes = nextBPlusNode.getBPlusNodes();
nextChildBPlusNodes.addAll(BPlusNode.getBPlusNodes());
//将当前节点的孩子节点的父节点设为当前节点的后节点
for (BPlusNode BPlusNode1 : childBPlusNodes1) {
BPlusNode1.setParantBPlusNode(nextBPlusNode);
}
Collections.sort(nextKeyAndValues);
merge(parentBPlusNode, key);
return true;
}
return false;
}
}
//得到当前节点的前节点
private BPlusNode getPreviousBPlusNode(BPlusNode BPlusNode) {
if (BPlusNode.isRoot()) {
return null;
}
BPlusNode parentBPlusNode = BPlusNode.getParantBPlusNode();
//得到兄弟节点
List<BPlusNode> BPlusNodes = parentBPlusNode.getBPlusNodes();
List<KeyAndValue> keyAndValues = new ArrayList<>();
for (BPlusNode n : BPlusNodes) {
List<KeyAndValue> list = n.getKeyAndValue();
int maxKeyAndValue = list.get(list.size() - 1).getKey();
if (maxKeyAndValue < getMinKeyInBPlusNode(BPlusNode)) {
keyAndValues.add(new KeyAndValue(maxKeyAndValue, n));
}
}
Collections.sort(keyAndValues);
if (keyAndValues.isEmpty()) {
return null;
}
return (BPlusNode) keyAndValues.get(keyAndValues.size() - 1).getValue();
}
//得到当前节点的后节点
private BPlusNode getNextBPlusNode(BPlusNode BPlusNode) {
if (BPlusNode.isRoot()) {
return null;
}
BPlusNode parentBPlusNode = BPlusNode.getParantBPlusNode();
//得到兄弟节点
List<BPlusNode> BPlusNodes = parentBPlusNode.getBPlusNodes();
List<KeyAndValue> keyAndValues = new ArrayList<>();
for (BPlusNode n : BPlusNodes) {
List<KeyAndValue> list = n.getKeyAndValue();
int minKeyAndValue = list.get(0).getKey();
if (minKeyAndValue > getMaxKeyInBPlusNode(BPlusNode)) {
keyAndValues.add(new KeyAndValue(minKeyAndValue, n));
}
}
Collections.sort(keyAndValues);
if (keyAndValues.isEmpty()) {
return null;
}
return (BPlusNode) keyAndValues.get(0).getValue();
}
private int getMinKeyInBPlusNode(BPlusNode BPlusNode) {
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
return keyAndValues.get(0).getKey();
}
private int getMaxKeyInBPlusNode(BPlusNode BPlusNode) {
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
return keyAndValues.get(keyAndValues.size() - 1).getKey();
}
private int getLeftBoundOfKey(BPlusNode BPlusNode, int key) {
int left = 0;
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
for (int i = 0; i < keyAndValues.size(); i++) {
if (keyAndValues.get(i).getKey() <= key && keyAndValues.get(i + 1).getKey() > key) {
left = keyAndValues.get(i).getKey();
break;
}
}
return left;
}
private int getRightBoundOfKey(BPlusNode BPlusNode, int key) {
int right = 0;
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
for (int i = 0; i < keyAndValues.size(); i++) {
if (keyAndValues.get(i).getKey() <= key && keyAndValues.get(i + 1).getKey() > key) {
right = keyAndValues.get(i + 1).getKey();
break;
}
}
return right;
}
private void delKeyAndValue(List<KeyAndValue> keyAndValues, int key) {
for (KeyAndValue keyAndValue : keyAndValues) {
if (keyAndValue.getKey() == key) {
keyAndValues.remove(keyAndValue);
break;
}
}
}
//找到BPlusNode的键值对中在min和max中的键值对
private KeyAndValue getKeyAndValueinMinAndMax(BPlusNode BPlusNode, int min, int max) {
if (BPlusNode == null) {
return null;
}
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
KeyAndValue keyAndValue = null;
for (KeyAndValue k : keyAndValues) {
if (k.getKey() > min && k.getKey() <= max) {
keyAndValue = k;
break;
}
}
return keyAndValue;
}
private KeyAndValue getMinKeyAndValueInChildBPlusNode(BPlusNode BPlusNode) {
if (BPlusNode.getBPlusNodes() == null || BPlusNode.getBPlusNodes().isEmpty()) {
return null;
}
List<KeyAndValue> sortKeyAndValues = new ArrayList<>();
List<BPlusNode> childBPlusNodes = BPlusNode.getBPlusNodes();
for (BPlusNode childBPlusNode : childBPlusNodes) {
List<KeyAndValue> keyAndValues = childBPlusNode.getKeyAndValue();
KeyAndValue minKeyAndValue = keyAndValues.get(0);
sortKeyAndValues.add(minKeyAndValue);
}
Collections.sort(sortKeyAndValues);
return sortKeyAndValues.get(0);
}
private boolean isNeedMerge(BPlusNode BPlusNode) {
if (BPlusNode == null) {
return false;
}
List<KeyAndValue> keyAndValues = BPlusNode.getKeyAndValue();
return keyAndValues.size() < rank / 2;
}
//判断一个节点是否有富余的键值对
private boolean isMoreElement(BPlusNode BPlusNode) {
return BPlusNode != null && (BPlusNode.getKeyAndValue().size() > rank / 2);
}
public static void main(String[] args) {
BPlusTree BPlusTree = new BPlusTree(4 );
KeyAndValue keyAndValue = new KeyAndValue(1,"123");
KeyAndValue keyAndValue1 = new KeyAndValue(2,"123");
KeyAndValue keyAndValue2 = new KeyAndValue(3,"123");
KeyAndValue keyAndValue3 = new KeyAndValue(4,"123");
KeyAndValue keyAndValue4 = new KeyAndValue(5,"123");
KeyAndValue keyAndValue5 = new KeyAndValue(6,"123");
KeyAndValue keyAndValue6 = new KeyAndValue(7,"12300");
KeyAndValue keyAndValue7 = new KeyAndValue(8,"546");
KeyAndValue keyAndValue8 = new KeyAndValue(9,"123");
KeyAndValue keyAndValue9 = new KeyAndValue(10,"123");
KeyAndValue keyAndValue10 = new KeyAndValue(11,"123");
KeyAndValue keyAndValue11 = new KeyAndValue(12,"123");
KeyAndValue keyAndValue12 = new KeyAndValue(13,"123");
KeyAndValue keyAndValue14 = new KeyAndValue(15,"12345");
KeyAndValue keyAndValue15 = new KeyAndValue(16,"12345");
KeyAndValue keyAndValue16 = new KeyAndValue(17,"12345");
KeyAndValue keyAndValue17 = new KeyAndValue(18,"12345");
KeyAndValue keyAndValue18 = new KeyAndValue(19,"12345");
KeyAndValue keyAndValue19 = new KeyAndValue(20,"12345");
KeyAndValue keyAndValue20 = new KeyAndValue(21,"12345");
BPlusTree.insert(keyAndValue);
BPlusTree.insert(keyAndValue5);
BPlusTree.insert(keyAndValue9);
BPlusTree.insert(keyAndValue1);
BPlusTree.insert(keyAndValue7);
BPlusTree.insert(keyAndValue10);
BPlusTree.insert(keyAndValue17);
BPlusTree.insert(keyAndValue2);
BPlusTree.insert(keyAndValue14);
BPlusTree.insert(keyAndValue16);
BPlusTree.insert(keyAndValue11);
BPlusTree.insert(keyAndValue12);
BPlusTree.insert(keyAndValue3);
BPlusTree.insert(keyAndValue8);
BPlusTree.insert(keyAndValue18);
BPlusTree.insert(keyAndValue15);
BPlusTree.insert(keyAndValue4);
BPlusTree.insert(keyAndValue19);
BPlusTree.insert(keyAndValue6);
BPlusTree.insert(keyAndValue20);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(1);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(0);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(2);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(11);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(3);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(4);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(5);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(9);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(6);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(13);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(7);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(10);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(18);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(8);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(12);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(20);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(19);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(15);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
BPlusTree.delete(17);
BPlusTree.printBPlusTree(BPlusTree.getRoot());
}
}