B+树原理以及Java代码实现

最初查找二叉树,由于树的高度会随着有序序列输入而急剧增长,后来出现平衡二叉树,红黑树。B树可以海量数据的快速查询检索,B树主要分为B树(B-树),B+树,B*树等。

B树(B-树)

M路搜索树,参数M定义节点的分支个数;

对于根节点孩子数目为[2,M],对于其余节点孩子数目为[M/2,M];

每个节点含有关键字属性,至少M/2-1  至少M-1;关键字个数=孩子个数-1;

节点有两种类型:  叶子节点    处于同一层

非叶子结点   关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];即关键字时有序的;孩子指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;

关键字集合分布在整颗树中;任何一个关键字出现且只出现在一个结点中;搜索有可能在非叶子结点结束;其搜索性能等价于在关键字全集内做一次二分查找;

B+树

对于B树的改进,每个节点具有关键字以及孩子指针属性:

非叶子结点的子树指针与关键字个数相同;

非叶子结点的子树指针P[i],指向关键字值属于**[K[i], K[i+1])**的子树(B-树是开区间);

为所有叶子结点增加一个链指针;

所有关键字都在叶子结点出现;

所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;不可能在非叶子结点命中;非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;更适合文件索引系统;

B*树

+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针;

B*树定义非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);

B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;

B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;B*树分配新结点的概率比B+树要低,空间使用率更高;

B+树Java代码实现

实现B+的构造,插入以及查询方法

定义B+树节点关键字以及孩子指针数据结构

定义B+树参数M

对于每个节点  孩子指针利用Entry[M]来表示,m表示关键字的个数;Entry表示具体的关键字信息  Key  Value以及Next指针

public class BTree<Key extends Comparable<Key>, Value> { //参数M 定义每个节点的链接数 private static final int M = 4; private Node root; //树的高度 最底层为0 表示外部节点 根具有最大高度 private int height; //键值对总数 private int n; //节点数据结构定义 private static final class Node{ //值的数量 private int m; private Entry[] children = new Entry[M]; private Node(int k){ m = k; } } //节点内部每个数据项定义 private static class Entry{ private Comparable key; private final Object val; //下一个节点 private Node next; public Entry(Comparable key, Object val, Node next){ this.key = key; this.val = val; this.next = next; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Entry [key="); builder.append(key); builder.append("]"); return builder.toString(); } }

查询方法

根据B+树定义,非叶子结点存储索引信息,叶子结点存储数据信息,利用height来判断当前是哪一层,如果height=0说明,当前节点为叶子结点,对于叶子结点,则执行顺序查找即可;如果当前节点为非叶子节点,则查找key在当前节点的哪一个孩子指针中,如果key大于当前节点所有关键字即j==m-1     如果key小于某个关键字j+1  则说明key位于j孩子指针中。

public Value get(Key key){ return search(root, key, height); } private Value search(Node x, Key key, int ht) { Entry[] children = x.children; //外部节点 这里使用顺序查找 //如果M很大 可以改成二分查找 if(ht == 0){ for(int j=0; j<x.m; j++){ if(equal(key, x.children[j].key)) return (Value)children[j].val; } } //内部节点 寻找下一个节点 else{ for(int j=0; j<x.m; j++){ //最后一个节点 或者 插入值小于某个孩子节点值 if(j+1==x.m || less(key, x.children[j+1].key)) return search(x.children[j].next, key, ht-1); } } return null; }

插入方法

利用递归思想,insert实现在某个节点中插入数据,如果当前节点为叶子结点,则找到待插入的位置j,将j以后数据后移一位,将当前数据插入,插入之后如果当前节点已满,则将当前节点的后M/2个元素产生一个新节点返回;如果当前节点为非叶子节点,找到待插入数据位于哪一个孩子节点中,递归调用insert方法,如果返回值非空,则说明下一层插入时,出现节点分裂,将新节点指向父节点;

public void put(Key key, Value val){ //插入后的节点 如果节点分裂,则返回分裂后产生的新节点 Node u = insert(root, key, val, height); n++; //根节点没有分裂 直接返回 if(u == null) return; //分裂根节点 Node t = new Node(2); //旧根节点第一个孩子 新分裂节点 t.children[0] = new Entry(root.children[0].key, null, root); t.children[1] = new Entry(u.children[0].key, null, u); root = t; height++; } private Node insert(Node h, Key key, Value val, int ht) { int j; //新建待插入数据数据项 Entry t = new Entry(key, val, null); // 外部节点 找到带插入的节点位置j if(ht == 0){ for(j=0; j<h.m; j++){ if(less(key, h.children[j].key)) break; } }else{ //内部节点 找到合适的分支节点 for(j=0; j<h.m; j++){ if(j+1==h.m || less(key, h.children[j+1].key)){ Node u = insert(h.children[j++].next, key, val, ht-1); if(u == null) return null; t.key = u.children[0].key; t.next = u; break; } } } //System.out.println(j + t.toString()); //j为带插入位置,将顺序数组j位置以后后移一位 将t插入 for(int i=h.m; i>j; i++){ h.children[i] = h.children[i-1]; } System.out.println(j + t.toString()); h.children[j] = t; h.m++; if(h.m < M) return null; //如果节点已满 则执行分类操作 else return split(h); } private Node split(Node h) { //将已满节点h的后一般M/2节点分裂到新节点并返回 Node t = new Node(M/2); h.m = M / 2; for(int j=0; j<M/2; j++) t.children[j] = h.children[M/2+j]; return t; }

节点分裂过程

在下图M=3中插入19,首先找到19的插入位置,插入;

插入之后,出现节点已满

将已满节点进行分裂,将已满节点后M/2节点生成一个新节点,将新节点的第一个元素指向父节点;

父节点出现已满,将父节点继续分裂

一直分裂,如果根节点已满,则需要分类根节点,此时树的高度增加

对于根节点分裂,新建一个节点  将旧根节点第一个元素以及分类节点第一个元素组合作为新的根节点。

public void put(Key key, Value val){ //插入后的节点 如果节点分裂,则返回分裂后产生的新节点 Node u = insert(root, key, val, height); n++; //根节点没有分裂 直接返回 if(u == null) return; //分裂根节点 Node t = new Node(2); //旧根节点第一个孩子 新分裂节点第一个孩子组成新节点作为根 t.children[0] = new Entry(root.children[0].key, null, root); t.children[1] = new Entry(u.children[0].key, null, u); root = t; height++; }

性能:

含有N个元素的M阶B+树,一次查找或者插入的时间复杂度为log(M)N~log(M/2)N,当M较大时,该值基本为常数;

https://mp.weixin.qq.com/s/vkvYJnKfQyuUeD_BDQy_1g

获取更多学习资料,可以加群:473984645或扫描下方二维码

点赞
收藏

评论区

加载中...

相关推荐

js实现二叉树、二叉查找树

树是一种数据结构,该章节讨论二叉树(二叉树的每个节点的子节点不允许超过两个),二叉树中有又分为完全二叉树和不完全二叉树.....不在本章节赘述相关概念,感兴趣可以去查阅《数据结构》。你将会获得:1.如何使用js实现二叉查找树。2.学会前、中、后序遍历。3.了解相关实现原理阅读时长5min,可选择直接调试代码特点    二叉查找树中序遍历后

树与二叉树

二叉树层次建树二叉树(BinaryTree)是由有限个结点组成的集合,它或者为空集合,或者仅含一个根结点,或者由一个根结点和两棵互不相交的左、右子树组成。树为空即根节点为空。二叉树的基本形态如下图:a:空的二叉树,treeNULL。b:只有空节点的二叉树,

高级java面试题,附答案+考点

蚂蚁金服一面1.两分钟的自我介绍2.二叉搜索树和平衡二叉树有什么关系,强平衡二叉树(AVL树)和弱平衡二叉树(红黑树)有什么区别3.B树和B树的区别,为什么MySQL要使用B树4.HashMap如何解决Hash冲突5.epoll和poll的区别,及其应用场景6.简述线程池原理,FixedThreadPoo

彻底搞懂系列B-树、B+树、B-树、B*树

(https://blog.csdn.net/chai471793/article/details/99563704)平衡二叉树概念平衡二叉树是基于二分法的策略提高数据的查找速度的二叉树的数据结构;特点平衡二叉树是采用二分法思维把数据按规则组装成一个树形结构的数据,用这个树形结构的数据减少无关数据的检索,大大

算法笔记:红黑树

红黑树,一种平衡二叉树,最为著名的应用就是CSTL中的map,是有序集合最为理想的存储方式之一。除了二叉树所具有的属性之后,红黑树中每个节点多了一个“颜色”属性,可以是红色或者是黑色。一棵红黑树应该满足一下的性质:1.每个节点是红色或者黑色的;2.根节点是黑色的;3.每个叶节点nil是黑色的(使用哨兵节点在删除调整时可以方便不少);4.如

B树与B+树的区别?

1.B树简介B树是一种多路平衡搜索树。它由二叉树变换而来的。定义如下:1.1每个节点最多有m1个关键字1.2根节点最少有1个关键字1.3非根节点至少有m/2个关键字1.4每个节点的关键字都是按照从小到大的顺序排列,每个关键字的左子树中的关键字都小于它,而右子树中所有关键字都大于它。1.5所有的叶子节点都处于同