树——B树

树简介

什么是B树

B树(Balanced Tree)是一种优秀的数据结构,用一种通俗的话来说:B树是一种一个节点可拥有多于两个子节点的树

B树有两种衡量标准:阶,度。

t度 的B树就是 2t阶 的B树(这也是B树的分裂机制决定的)

因为t度的B树节点最多有2t个孩子,2t-1个关键字;m阶的B树最多有m个孩子,

其实通过度定义的B树和通过阶数定义的B树,区别就是一个是用的这个B树节点的最小度数一个是用的这个树节点的最大度数。

这里我们统一使用阶来描述树。

一个t阶B树有以下约束:【注意,所有的除法都向上取整】

  • 根节点除外,每个节点最少有t/2个孩子,最多有t个孩子【也就是说一个节点最少有t/2-1个值,最多有t-1个值,根节点除外】
  • 根节点至少有两个孩子
  • 同一节点x[i],x[i+1]之间的指针指向的子树的值必须在x[i],x[i+1]之间

B树的实例

比较有名的B树有2-3树,2-3-4树。

  • 其中2-3树是度数为1的树,即最多有三个子树,也就是每个节点可以放1,2个值
  • 2-3-4树是度数为2的树,最多有四个子树,每个节点可以放1,2,3个值

B树,B-树的联系和区别

B树和B-树是一个东西,会出现这两种说法只是翻译时出的意外

B树的英文名称是B-Tree。有的人在翻译时把横线翻译成连接符,所以就翻译成了B树。有的人认为是减号,就翻译成了B-树,正好还有B+,B*,所以即便翻译成B-也还凑合着说的过去,所以想叫什么就看自己喽。

B树,B+树的区别和联系

B+树是对B树的一种变形树,它与B树的差异在于:

  • 非叶结点仅具有索引作用,跟记录有关的信息均存放在叶结点中。
  • 树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录。

B和B+树的区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历。

B+ 树的优点是:

  • 由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。
  • B+树的叶子结点都是相链的,因此对整棵树的便利只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

但是B树也有优点,其优点在于,由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。

B+树和B*树的却别和联系

B*树是B+树的变体,在B+树的基础上,B*树中非根和非叶子结点再增加指向兄弟的指针;B*树定义了阶为M的树非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2)

平衡树实现的思考

平衡树的出现是因为。搜索树的分布一般情况下很不平衡,导致搜索最坏情况下的搜索树深度可能达到O(n)。而二叉搜索树的结构完全由节点插入的顺序决定【他是对输入敏感的】,树不具有分配节点去向的能力,所以也无法根据当前的情况动态的改变树的高度,就容易出现极端的情况

所以我们需要设计一种结构,能使根节点拥有对新来的子节点的动态分配权,增加根节点分配权的方法就是让他可以比,让根节点有可以权衡树平衡程度的信息

  • AVL树的操作思路是先让节点插进去,然后判断,不合适就旋转,把自己转过去,增加兄弟节点的树深
  • 笛卡尔树是根据额外的一个value通过堆的思想来决定节点的去向
  • Treap和笛卡尔树相似,不过它是一个单纯的BBST,所以它的value随机生成,用来平衡树的深度

B树不是二叉搜索树,但是你也可以把他看成二叉搜索树,它用来平衡的方法是缓存:

以2-3树为例,来了一个值后如果能放在这个节点,这个节点就先存着,等这个节点放不下了,然后再统一决定节点的去向。

树操作算法概述

B树

例子:2-3树定义

2-3树是每个非叶子节点最多有三个子节点。

  • 你可以把它理解成在二叉搜索树的基础上增加了一种新的节点——里面有两个键值,有三个子树。
  • 你也可以用一种更加通俗的名字叫它——3阶B树

例子:2-3-4 树定义

2-3-4树就是4阶B树。

树的操作方法

我们这里主要介绍B树的插入操作和删除操作。

搜索

之前我们写二叉树是小于就去左边找,大于就去右边找。这里在和本节点中的值比较时就从左往右顺序比较即可,找到比inputKey大的key[i]后,顺着key[i]的左子树继续向下查询。

插入

B树的插入有以下约束:

  1. 只有叶子节点可以插入值
  2. 节点溢出后进行分裂时,新构建的父节点一定要插入到原来的父节点的位置

其实这两个约束也就是我们插入B树的思路:

  1. 通过搜索找到合适插入的叶子节点【插入一定是在叶子节点中
  2. 插入
  3. 判断是否溢出,溢出则分裂,将中值移动至父节点
  4. 判断父节点是否溢出。。。。。。。
  5. 判断父节点的父节点。。。。。
  6. 一直到根节点

引用程序员修炼之路的博客的一句话就是:

插入一个元素时,首先在B树中是否存在,如果不存在,即在叶子结点处结束,然后在叶子结点中插入该新的元素,注意:如果叶子结点空间足够,这里需要向右移动该叶子结点中大于新插入关键字的元素,如果空间满了以致没有足够的空间去添加新的元素,则将该结点进行“分裂”,将一半数量的关键字元素分裂到新的其相邻右结点中,中间关键字元素上移到父结点中(当然,如果父结点空间满了,也同样需要“分裂”操作),而且当结点中关键元素向右移动了,相关的指针也需要向右移。如果在根结点插入新元素,空间满了,则进行分裂操作,这样原来的根结点中的中间关键字元素向上移动到新的根结点中,因此导致树的高度增加一层。

删除

讲道理,删除还真的有点复杂,这个涉及到从兄弟节点、父节点的借值。思路如下:

  1. 通过搜索找到要删除的值所在的节点
  2. 删除此值,然后使用子树的值来替换:
    1. 如果这个值的左子树可以借值的话【去掉一个值还满足B树的要求】,就用左子树的最右节点来代替
    2. 如果左子树不行,但是右子树可以的话,就用右子树的最左节点来代替
  3. 子树的值不能满足替换,如果它的左右兄弟节点可以,就通过【兄弟节点——相同的父节点——本节点】的方式进行借值
  4. 如果兄弟节点也不行,就和相近的兄弟节点合并成为一个新的节点

关键代码

直接放项目的地址了,我是用的java写的增删操作,没有用C。还有就是没有专门为这个建git,源码在com.gateway.learn.tree下面。github地址:https://github.com/LiPengcheng1995/gateway-parent.git

插入

这里,插入操作的关键算法如下:

/**
     * @Author: lipengcheng20
     * @Date: 2018/9/28 14:23
     * @Description: 对外暴露,用于在此节点或此节点的子节点中插入key
     **/


    public BTreeNode findAPlaceAndInsert(int key) {
        if (this.getData().size() == 0) {
            this.initNodeWithAKey(key);
            return this;
        }
        if (this.getSubTreeNumber() == 0) {
            // 是叶子节点,就在这里插了
            // 此节点定不为空
            this.insertKey(key);
            return this.checkOverFlowAndReturn();
        } else {
            for (int i = 0; i < this.getKeyNumber(); i++) {
                if (this.getKeyByIndex(i) > key) {
                    //插入到这个的左子树中
                    if (Objects.isNull(this.getSubTreeOnLeftOf(i))) {
                        //子树对应的为空,直接插进去
                        BTreeNode temp = new BTreeNode(this.getMAX_SUBTREE_NUMBER());
                        temp.initNodeWithAKey(key);
                        this.setSubTreeOnLeftOf(i, temp);
                        return this;
                    } else {
                        //对应的子树不为空,直接在子树中插,然后根据实际情况看是不是要分裂
                        BTreeNode temp = this.getSubTreeOnLeftOf(i).findAPlaceAndInsert(key);
                        if (temp.getData().size() == 3) {
                            //子节点做了分离
                            this.insertKeyByKeyIndex(i, temp.getKeyByIndex(0));
                            this.setSubTreeOnLeftOf(i, temp.getSubTreeOnLeftOf(0));
                            this.setSubTreeOnRightOf(i, temp.getSubTreeOnRightOf(0));
                        }
                        return checkOverFlowAndReturn();
                    }
                }
            }

            //插入到最右边的子树
            if (Objects.isNull(this.getSubTreeOnRightOf(this.getKeyNumber() - 1))) {
                //最右边子树对应的为空,直接插进去
                BTreeNode temp = new BTreeNode(this.getMAX_SUBTREE_NUMBER());
                temp.initNodeWithAKey(key);
                this.setSubTreeOnRightOf(this.getKeyNumber() - 1, temp);
                return this;
            } else {
                //最右边对应的子树不为空,直接在子树中插,然后根据实际情况看是不是要分裂
                BTreeNode temp = this.getSubTreeOnRightOf(this.getKeyNumber() - 1).findAPlaceAndInsert(key);
                if (temp.getData().size() == 3) {
                    //子节点做了分离
                    this.insertKeyToTheEnd(temp.getKeyByIndex(0));
                    this.setSubTreeOnLeftOf(this.getKeyNumber() - 1, temp.getSubTreeOnLeftOf(0));
                    this.setSubTreeOnRightOf(this.getKeyNumber() - 1, temp.getSubTreeOnRightOf(0));
                }
                return checkOverFlowAndReturn();
            }
        }
    }

删除

只写了伪代码,感觉有点复杂,就先不写了

B+树实现

太难太难

源码

直接放项目的地址了,我是用的java写的增删操作,没有用C。还有就是没有专门为这个建git,源码在com.gateway.common.web.learnTree下面。github地址:https://github.com/LiPengcheng1995/try.git

树应用场景

主要是数据库的搜索和文件系统的搜索。通过多叉树,这样可以一次读取多个节点,减少换页。B+树尤其如此,B+树非叶子节点只存储了“主键”。

参考文献

https://www.cnblogs.com/vincently/p/4526560.html
https://www.cnblogs.com/hdk1993/p/5840599.html

应用:http://blog.codinglabs.org/articles/theory-of-mysql-index.html

规律:
https://blog.csdn.net/pleasecallmewhy/article/details/8451889
https://blog.csdn.net/guoziqing506/article/details/64122287

你可能感兴趣的:(树——B树)