B树就是个树,和二叉树的区别是,二叉树只能有两个子节点,所以二叉树的劣势在于深度过深了
二叉树有点分治法里的二分,而B树就有点彻底的分治法的意思了
B树的内节点(即非叶节点)上也是有值的,为关键字,关键字的值是要有顺序的(我的代码里默认升序,关键字的值只是一个整数,其实可以保存对象)
B树的关键字个数不是随意的
1)我们有一个度数用t来表示,t>=2
2)关键字的个数m,m>=t-1,m<=2t-1,根节点不受限制最小限制,如果非叶节点,子节点个数为关键字个数+1
3)当关键字的个数m=2t-1时,这就是个满节点
4)每个叶节点要有相同的深度
要满足上面这些要求,所以添加和删除节点时就不能随意
添加:
1)添加只能添加到叶节点上,如果添加之后,改节点的关键字个数未超过限制,则结束
2)如果关键字个数为2*t个,提取第t个数字,将节点分割成两个节点,第t个数插入父节点中为关键字,如果此时父节点也关键字数溢出,再进行分割,直至结束
删除:
1)如果删除的值不存在,则不进行删除
2)如果删除的值在内节点上,将最邻近改节点的叶节点关键字(即该关键字左侧节点的最右子节点的最右子节点的…最右关键字 或 右侧节点的最左子节点的最左子节点的…最左关键字,我之所以选择左侧节点左右关键字,是因为我关键字是按数组存储,删除最右比删除最左简单很多)赋值到该节点,删除该邻近关键字,此时操作和3)一致
3)如果删除之后的关键字数不小于t-1个,就结束否则执行4)
4)找兄弟节点,如果兄弟节点大于t-1个,问兄弟节点借一个关键字(不是直接借,兄弟节点的值给父节点中两人的夹位,去此处的关键字,给自己),如果兄弟节点为t-1个关键字,执行5)
5)合并自己与兄弟节点,把两者夹位数也拿下来,组成新的节点,此时父节点会少一个关键字,判断父节点的后续操作(循环3、4、5)
附:不知道别的是用数组还是链表,查找时候没有用更快的二分查找,想着数组长度也不长,就直接顺序查找
因为书中只讲了B树没有讲B+树,所以就没B+的代码实现,有机会的话之后再实现
B+树与B树的区别:
最大区别在于,B+树所有的数据存在叶节点上,其他没有本质区别
红黑树:https://blog.csdn.net/qq_33321609/article/details/86702550
感觉红黑树最难,旋转就转死我了(数个月前写的,我现在看着都看不懂了)
效率分析
B树
取t值为30时
插入100w条数据,耗时227ms
删除0-100w数值各一次,耗时89ms
取t值为2时
插入100w条数据,耗时709ms
删除0-100w数值各一次,耗时142ms
红黑树
插入100w条数据,耗时935ms
删除0-100w数值各一次,耗时220ms
红黑树和B树t取值为2时相近
而且B树在内存使用上更少
当然,这些比较都是我的代码,所以不具有权威
这些都比数组和链表快
数组慢在于插入和删除(查询可以使用二分查找),链表在查询时候会慢(无论插入还是删除,都要先查询在哪操作)
代码实现:
Tree类
import java.util.ArrayList;
import java.util.List;
public class Tree {
int t = 0;
int num = 0;
int[] keys;
Tree[] children;
boolean haveCh;
public Tree(int t) {
this.t = t;
keys = new int[2 * t];
children = new Tree[2 * t + 1];
}
public Tree(int t, int[] keys, int sta, int end) {
this(t);
for (int i = sta; i <= end; i++) {
this.keys[num++] = keys[i];
}
}
public Tree(int t, int[] keys, int sta, int end, Tree[] children) {
this(t);
haveCh = true;
for (int i = sta; i <= end; i++) {
this.children[num] = children[i];
this.keys[num++] = keys[i];
}
this.children[num] = children[end + 1];
}
public AddReNode add(int value) {
if (!haveCh) {
//如果没有子节点
int i = 0;
for (; i < num; i++) {
if (keys[i] >= value) {
for (int j = num; j > i; j--) {
keys[j] = keys[j - 1];
}
break;
}
}
keys[i] = value;
num++;
//如果关键字个数过多
if (num == 2 * t) {
//把节点分成两个节点
AddReNode addReNode = new AddReNode();
addReNode.key = keys[t];
addReNode.trees[0] = new Tree(t, keys, 0, t - 1);
addReNode.trees[1] = new Tree(t, keys, t + 1, 2 * t - 1);
this.keys[0] = keys[t];
this.children[0] = addReNode.trees[0];
this.children[1] = addReNode.trees[1];
this.num = 1;
this.haveCh = true;
return addReNode;
} else {
return null;
}
} else {
//如果有子节点
int i = 0;
for (; i < num; i++) {
if (keys[i] >= value) {
break;
}
}
//在子节点上添加
AddReNode addReNode = children[i].add(value);
if (addReNode != null) {
//如果子节点添加后分裂
for (int j = num; j > i; j--) {
keys[j] = keys[j - 1];
children[j + 1] = children[j];
}
keys[i] = addReNode.key;
children[i] = addReNode.trees[0];
children[i + 1] = addReNode.trees[1];
num++;
//分裂
if (num == 2 * t) {
AddReNode nddReNode = new AddReNode();
nddReNode.key = keys[t];
nddReNode.trees[0] = new Tree(t, keys, 0, t - 1, children);
nddReNode.trees[1] = new Tree(t, keys, t + 1, 2 * t - 1, children);
this.keys[0] = keys[t];
this.children[0] = nddReNode.trees[0];
this.children[1] = nddReNode.trees[1];
this.num = 1;
this.haveCh = true;
return nddReNode;
}
}
return null;
}
}
//删除
public boolean delete(int value) {
int all = num;
int i = 0;
for (; i < num; i++) {
if (value < keys[i]) {
if (haveCh) {
boolean b = children[i].delete(value);
if (b) {
//找右兄弟
this.removeRight(i);
}
}
break;
}
if (value == keys[i]) {
if (!haveCh) {
for (int j = i; j < num - 1; j++) {
keys[j] = keys[j + 1];
}
num--;
break;
}
Tree leftRight = getRight(children[i]);
keys[i] = leftRight.keys[leftRight.num - 1];
boolean b = children[i].removeRight();
if (b) {
//确保有有兄弟,所以找由兄弟
this.removeRight(i);
}
break;
}
}
//说明前面一直没有删,那就再去判断最右一个child
if (i == all) {
if (haveCh) {
boolean b = children[num].delete(value);
if (b) {
//找最右节点的左兄弟
this.removeTopLeft();
}
}
}
return num < t - 1;
}
//如果节点数不够,和右节点合并或者借
private void removeRight(int key) {
if (children[key + 1].num == t - 1) {
//不够,合并
children[key].num += 1 + t - 1;
children[key].keys[t - 2] = keys[key];
for (int j = 0; j < t - 1; j++) {
children[key].keys[t - 1 + j] = children[key + 1].keys[j];
children[key].children[t - 1 + j] = children[key + 1].children[j];
}
children[key].children[2 * t - 2] = children[key + 1].children[t - 1];
//后面的要移过来
for (int j = key; j < num - 1; j++) {
keys[j] = keys[j + 1];
children[j+1] = children[j + 2];
}
num--;
} else {
//如果右侧的兄弟节点够,就借
children[key].num++;
children[key].keys[children[key].num - 1] = keys[key];
children[key].children[children[key].num] = children[key + 1].children[0];
keys[key] = children[key + 1].keys[0];
children[key + 1].removeLeft();
}
}
//删除最右的关键字
private boolean removeRight() {
if (haveCh) {
boolean b = children[num].removeRight();
if (b) {
//因为没有右兄弟,所以找左兄弟
this.removeTopLeft();
}
} else {
num--;
}
return num < t - 1;
}
//虽然叫TopLeft,其实是删除最右的节点,如果节点数不够,和其左节点合并
private void removeTopLeft() {
if (children[num - 1].num == t - 1) {
//如果兄弟节点恰好是最少值,就进行合并
children[num - 1].num += 1 + t - 2;
children[num - 1].keys[t - 1] = keys[num - 1];
for (int i = 0; i < t - 2; i++) {
children[num - 1].keys[t + i] = children[num].keys[i];
children[num - 1].children[t + i] = children[num].children[i];
}
children[num - 1].children[2 * t - 2] = children[num].children[t - 2];
num--;
} else {
//问兄弟节点借一个
children[num - 1].num--;
int newChN = children[num - 1].num;
children[num].addLeft(keys[num - 1], children[num - 1].children[newChN + 1]);
keys[num - 1] = children[num - 1].keys[newChN];
}
}
//问左兄弟借来的节点
private void addLeft(int key, Tree node) {
num++;
children[num] = children[num - 1];
for (int i = num - 1; i >= 1; i--) {
keys[i] = keys[i - 1];
children[i] = children[i - 1];
}
keys[0] = key;
children[0] = node;
}
//把最左的节点借给左兄弟
private void removeLeft() {
for (int i = 0; i < num - 1; i++) {
keys[i] = keys[i + 1];
children[i] = children[i + 1];
}
num--;
children[num] = children[num + 1];
}
//获取最右的(因为使用数组,从右侧删除,方便)
private Tree getRight(Tree tree) {
if (tree.haveCh) {
return getRight(tree.children[tree.num]);
}
return tree;
}
//获取所有节点
public static List<Integer> show(Tree tree) {
List<Integer> ans = new ArrayList<>();
if (tree == null) {
return ans;
}
if (tree.haveCh) {
for (int i = 0; i < tree.num; i++) {
ans.addAll(show(tree.children[i]));
ans.add(tree.keys[i]);
}
ans.addAll(show(tree.children[tree.num]));
} else {
for (int i = 0; i < tree.num; i++) {
ans.add(tree.keys[i]);
}
}
return ans;
}
//校验关键字的数目
public static void checkTree(Tree tree, boolean ifTop) {
if (tree.num > 2 * tree.t - 1) {
System.out.println("存在关键字过多");
}
if (!ifTop) {
if (tree.num < tree.t - 1) {
System.out.println("存在关键字过少");
}
}
}
//树的深度
public static int deep(Tree tree) {
if (tree.haveCh) {
return 1 + deep(tree.children[0]);
}
return 1;
}
class AddReNode {
int key;
Tree[] trees = new Tree[2];
}
}
Test类
public class Test {
public static void main(String[] args) {
Tree tree = new Tree(30);
Random random = new Random();
for (int i = 0; i < 1000000; i++) {
int v = random.nextInt(1000000);
tree.add(v);
}
System.out.println("插入结束");
List<Integer> ans = Tree.show(tree);
//System.out.println(ans);
System.out.println(Tree.deep(tree));
System.out.println(ans.size());
for (int i = 1; i < ans.size(); i++) {
if (ans.get(i) < ans.get(i - 1)) {
System.out.println("顺序有误");
}
}
Tree.checkTree(tree, true);
System.out.println("开始删除");
//int len = ans.size();
for (int i = 0; i < 1000000; i++) {
tree.delete(i);
if (tree.num == 0) {
tree = tree.children[0];
}
/*ans = Tree.show(tree);
int dl = len-ans.size();
if(dl>1){
System.out.println("删除个数超过1");
}
len = ans.size();
for (int j = 1; j < ans.size(); j++) {
if (ans.get(j) < ans.get(j - 1)) {
System.out.println("顺序有误");
}
}*/
}
System.out.println(Tree.deep(tree));
ans = Tree.show(tree);
System.out.println(ans.size());
for (int i = 1; i < ans.size(); i++) {
if (ans.get(i) < ans.get(i - 1)) {
System.out.println("顺序有误");
}
}
Tree.checkTree(tree, true);
/*int deep = Tree.deep(tree);
while(Tree.deep(tree)==deep){
for (int i = 0; i < 1000000; i++) {
tree.delete(i);
if (tree.num == 0) {
tree = tree.children[0];
}
}
System.out.println(Tree.deep(tree));
ans = Tree.show(tree);
System.out.println(ans.size());
}
deep = Tree.deep(tree);
while(Tree.deep(tree)==deep){
for (int i = 0; i < 1000000; i++) {
tree.delete(i);
if (tree.num == 0) {
tree = tree.children[0];
}
}
System.out.println(Tree.deep(tree));
ans = Tree.show(tree);
System.out.println(ans.size());
}
deep = Tree.deep(tree);
while(Tree.deep(tree)==deep){
for (int i = 0; i < 1000000; i++) {
tree.delete(i);
if (tree.num == 0) {
tree = tree.children[0];
}
}
System.out.println(Tree.deep(tree));
ans = Tree.show(tree);
System.out.println(ans.size());
}
ans = Tree.show(tree);
System.out.println(ans);*/
}
}