数据结构(三)--- B树(B-Tree)

   文章图片代码来自邓俊辉老师的课件

概述

       08B3-2 操作实例.mp4_20190201_161433.852

     上图就是 B-Tree 的结构,可以看到这棵树和二叉树有点不同---“又矮又肥”。同时子节点可以有若干个小的子节点构成。那么这样一棵树又有什么作用呢?

动机

      我们知道电脑的访问内存比访问外的存I/O操作快了,但是内存的容量大小又只有那么一点点(相对于外存),所以计算机访问的过程常常使用高速缓存。使用高速缓存也是在以下两个事实想出的策略。

      08B1-5 分级IO.mp4_20190201_160449.241

08B1-6 1Bto1KB.mp4_20190201_160905.900

        而B-Tree这种结构就是根据这种情况被发掘出来的。下图 m 指的是每次的数据块数量

08B2-3 还是IO.mp4_20190201_161008.481

B-Tree 介绍

多路平衡

       关键码指的是一个超级节点包含的子节点。

08B2-2 多路平衡.mp4_20190201_160947.256

B-Tree定义

         m阶指的是m路,一个超级节点最大可以分出多少路。二叉树分出两边,左边和右边,就是两路,二阶。

08B2-4 深度统一.mp4_20190201_161101.020

        下面是几个定义为不同阶的B-树。

m阶实例

分支数

        B-Tree的分支数有个上下限,例如6阶的B-Tree(m=6),又被称为 “(3,6)-树”,类似的还有 “(3,5)-树”,“(2,4)-树”,而(2,4)树就是我们后面要学的红黑树。

08B2-5 阶次含义.mp4_20190201_161131.208

最大树高和最小数高

       可以看到对于“含N个关键码的m阶B-树”的最大树高和最小树高之间的波动并不大。

08B3-5 最大高度.mp4_20190201_214551.158

08B3-6 最小高度.mp4_20190201_214420.176

代码实现

      代码实现主要的两个方法为插入和删除。其中插入的时候需要注意查看某个节点是否超出了阶数,若超出了,需要分裂,最坏的情况就是分裂到根部,而删除操作需要注意查看是否会产生下溢,处理下溢,我们常用的方法就是旋转和合并。

插入

      下图分别是分裂和再分裂的图示。     分裂

再分裂

分裂实例

删除

       删除操作的旋转和合并。

       旋转可以理解为左右兄弟有足够的节点,向左右兄弟节点借来补充的操作。

旋转

       假如向兄弟们借都不成功,那就拿父节点的一个元素一起合并,代码实现中有分左合并和右合并。

合并

java版本代码

       B-树节点类

package BTree;

import java.util.Vector;

/**
 * B-树的节点Bean
 * 包含一个有序向量 value 和 指向子节点的 child 向量
 *
 */
public class BTreeNode {
    BTreeNode parent;
    Vector<BTreeNode> child;
    Vector<Integer> value;

    public BTreeNode(int value, BTreeNode left, BTreeNode right) {
        if (this.value == null) {
            this.value = new Vector<>();
            this.value.sort(new VectorComparable());
        }
        if (child == null) {
            child = new Vector<>();
        }
        this.value.add(value);
        this.child.add(0, left);
        this.child.add(1, right);
        if (left != null) {
            left.parent = this;
        }
        if (right != null) {
            right.parent = this;
        }
    }

    public BTreeNode() {
        parent = null;
        if (this.value == null) {
            this.value = new Vector<>();
            this.value.sort(new VectorComparable());
        }
        if (child == null) {
            child = new Vector<>();
        }
    }

    /**
     * 一个关键块内的查找 查找到与否都返回一个index
     * 返回最靠近的值的原因是为了下面的节点继续查找
     *
     * @param value 查找的值
     * @return 不存在的情况返回最靠近的index 值 , -1
     */
    public int search(int value) {
        int hot = -1;
        for (int i = 0; i < this.value.size(); i++) {
            if (this.value.get(i) > value) {
                return hot;
            } else if (this.value.get(i) < value) {
                hot = i;
            } else { // 相等
                return i;
            }
        }
        return this.value.size() - 1;
    }

    public int getIndexInValue(int compare) {
        for (int i = 0; i < this.value.size(); i++) {
            if (compare == value.get(i)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 查找当前node在父节点中的index
     *
     * @return -1 为父类不存在或是父类为null ,其他为当前节点在父节点为位置
     */
    public int getIndexFromParent() {
        if (parent == null) {
            return -1;
        }
        for (int i = 0; i < parent.child.size(); i++) {
            if (parent.child.get(i) == this) {
                return i;
            }
        }
        return -1;
    }

    public void addValue(int index, int val) {
        value.add(index, val);
        value.sort(new VectorComparable());
    }


    public void addValue(int val) {
        value.add(val);
        value.sort(new VectorComparable());
    }

}

      B-树数据结构方法。

package BTree;

public class BTree {
    private BTreeNode root;
    private int degree; // m阶B-树 ,阶树至少为3

    /*
     * 私有方法
     */


    /**
     * 查找在哪个BTreeNode ,假如到了外部节点,返回该外部节点 返回的结果只有两种 :
     * - 存在,返回该节点
     * - 不存在,返回值应该插入的节点
     *
     * @param val 查找的值
     * @return 返回搜索结果,假如该关键块不存在(到达了外部节点)就返回该关键快
     */
    private BTreeNode searchSurroundNode(int val) {
        BTreeNode node, hot = null;
        int rank;
        node = root;
        while (node != null) {
            rank = node.search(val);
            if (rank != -1 && node.value.get(rank) == val) { // 找到对应的值
                return node;
            } else {
                hot = node;
                if (node.child.get(rank + 1) == null) {
                    return hot;
                }
                node = node.child.get(rank + 1);
            }
        }
        // 到了外部节点
        return hot;
    }


    private void addNodeForBtNode(BTreeNode node, int rank, int val) {
        node.addValue(val);
        if (rank != -1) {
            node.child.add(rank + 2, null);
        } else {
            node.child.add(0, null);
        }
    }


    /*
     *  下面为可调用的方法
     */


    public BTree(int degree) {
        this.degree = degree;
    }

    /**
     * 返回值所在的节点
     *
     * @param val 插入的值
     * @return 找到的话返回节点,找不到返回 null
     */
    public BTreeNode search(int val) {
        BTreeNode node = searchSurroundNode(val);
        if (node.value.get(node.search(val)) == val) { // 该节点存在该值
            return node;
        }
        return null;
    }

    /**
     *
     * 插入的值都会进入到底部节点
     * @param val 插入的值
     * @return 是否插入成功
     */
    public boolean insert(int val) {
        if (root == null) {
            root = new BTreeNode(val, null, null);
            return true;
        }
        //root 已经创建,插入的值最终会到达底部,然后插进去
        BTreeNode node = searchSurroundNode(val);
        int rank = node.search(val);
        if (rank != -1 && node.value.get(rank) == val) { // 该节点存在该值,返回插入失败
            return false;
        } else { // 值将会插入该关键码
            addNodeForBtNode(node, rank, val);
            split(node);
            return true;
        }
    }


    private void split(BTreeNode node) {
        while (node.value.size() >= degree) {
            // 1.取中数
            int midIndex = node.value.size() / 2;
            BTreeNode rightNode = new BTreeNode();
            for (int i = midIndex + 1; i < node.value.size(); i++) {
                rightNode.addValue(node.value.remove(i));
                if (i == midIndex + 1) {
                    rightNode.child.add(node.child.remove(i));
                }
                rightNode.child.add(node.child.remove(i));
            }
            for (BTreeNode rn : rightNode.child) {
                if (rn != null) {
                    rn.parent = rightNode;
                }
            }

            // 移除原节点记得移除对应它的子节点
            int insertValue = node.value.remove(midIndex);
            if (node.parent != null) { // 存在父节点,把分裂点添加在父节点上
                node.parent.addValue(insertValue);
                /*
                 * 对插入的节点的子节点进行处理
                 * 1.得出插入点的index
                 * 2.左边子节点连接原node,右节点连接 rightNode
                 */
                int indexInValue = node.parent.getIndexInValue(insertValue);
                node.parent.child.add(indexInValue + 1, rightNode);
                rightNode.parent = node.parent;
                node = node.parent;
            } else { // 不存在父节点,并且当前节点溢出
                root = new BTreeNode(insertValue, node, rightNode);
                break;
            }
        }
    }

    public boolean delete(int val) {
        //node 为要删除的val所在的节点
        BTreeNode node = search(val);
        if (node != null) {
            int rank = node.getIndexInValue(val);
            // 找到继承结点并代替
            if (node.child.get(0) != null) { //非底部节点
                BTreeNode bottom = node.child.get(rank + 1);
                while (bottom.child.get(0) != null) {
                    bottom = bottom.child.get(0);
                }
                node.value.set(rank, bottom.value.get(0));
                bottom.value.set(0, val);
                node = bottom;
                rank = 0;
            }
            // 此时 node 一定是外部节点了(最底层)
            node.value.remove(rank);
            node.child.remove(rank + 1);
            // 由于删除了某个值,所以需要从兄弟中借一个来拼凑(旋转)
            // 当兄弟自己已到达下限,与父类合并成更大的节点,原来父节点所在的节点有可能-1后
            // 导致又达到了下限,然后循环
            solveUnderflow(node);
            return true;
        }
        return false;
    }

    /**
     * 下溢的节点 :
     * - 外部节点
     * - 非外部节点
     *
     * @param node 下溢的节点
     */
    public void solveUnderflow(BTreeNode node) {
        //没有达到下溢的条件
        int condition = (degree + 1) / 2;
        if (node.child.size() >= condition) {
            return;
        }
        BTreeNode parent = node.parent;
        if (parent == null) { //到了根节点
            if (node.value.size() == 0 && node.child.get(0) != null) {
                root = node.child.get(0);
                root.parent = null;
                node.child.set(0, null);
            }
            return;
        }
        int rank = node.getIndexFromParent();

        //旋转
        if (rank > 0 && parent.child.get(rank - 1).child.size() > condition) { //左旋转,从左兄弟拿一个
            BTreeNode ls = parent.child.get(rank - 1);
            node.addValue(0, parent.value.remove(rank - 1));
            parent.addValue(rank - 1, ls.value.remove(ls.value.size() - 1));
            /*
             *   被取走的节点可能存在子节点,需要放在新的位置
             *   有可能上一次进行合并操作中,父节点的关键码为空了,
             *   但是父节点还存在子节点(不为null)
             */
            node.child.add(0, ls.child.remove(ls.child.size() - 1));
            if (node.child.get(0) != null) {
                node.child.get(0).parent = node;
            }
            return;
        } else if (rank < parent.child.size() - 1 && parent.child.get(rank + 1).child.size() > condition) {  //右旋转,从右兄弟拿一个
            BTreeNode rs = parent.child.get(rank + 1);
            node.addValue(parent.value.remove(rank));
            parent.addValue(rs.value.remove(0));

            node.child.add(node.child.size(), rs.child.remove(0));
            if (node.child.lastElement() != null) {
                node.child.lastElement().parent = node;
            }
            return;
        }

        // 合并
        if (rank > 0) { // 左合并
            BTreeNode ls = parent.child.get(rank - 1);
            //父类节点转入到左节点
            ls.addValue(ls.value.size(), parent.value.remove(rank - 1));
            parent.child.remove(rank);
            //当前节点转入到左节点
            ls.child.add(ls.child.size(), node.child.remove(0));
            if (ls.child.get(ls.child.size() - 1) != null) {
                ls.child.get(ls.child.size() - 1).parent = ls;
            }
            // 当前节点有可能value为空,但是child不为空。
            // value 为空不移动,不为空移动
            while (node.value.size() != 0) {
                ls.addValue(node.value.remove(0));
                ls.child.add(ls.child.size(), node.child.remove(0));
                if (ls.child.get(ls.child.size() - 1) != null) {
                    ls.child.get(ls.child.size() - 1).parent = ls;
                }
            }

        } else {   //右合并,有可能 rank = 0
            BTreeNode rs = parent.child.get(rank + 1);
            //父类节点转入到右节点
            rs.addValue(0, parent.value.remove(rank));
            //父类节点断开与当前节点的连接
            parent.child.remove(rank);
            //当前节点转入到右节点
            rs.child.add(0, node.child.remove(0));
            if (rs.child.get(0) != null) {
                rs.child.get(0).parent = rs;
            }
            while (node.value.size() != 0) {
                rs.addValue(0, node.value.remove(0));
                rs.child.add(0, node.child.remove(0));
                if (rs.child.get(0) != null) {
                    rs.child.get(0).parent = rs;
                }
            }

        }
        solveUnderflow(parent);
    }

    public int height() {
        int h = 1;
        BTreeNode node = root;
        while (node != null) {
            if (node.child.get(0) != null) {
                h++;
                node = node.child.get(0);
            } else {
                break;
            }
        }
        return h;
    }

}

    最后是一个测试的方法。

package BTree;

public class BTreeTest {
    public static void main(String[] args) {
        BTree tree = new BTree(3);
        tree.insert(53);
        tree.insert(97);
        tree.insert(36);
        tree.insert(89);
        tree.insert(41);
        tree.insert(75);
        tree.insert(19);
        tree.insert(84);
        tree.insert(77);
        tree.insert(79);
        tree.insert(51);
//        System.out.println(tree.height());
//        tree.insert(23);
//        tree.insert(29);
//        tree.insert(45);
//        tree.insert(87);
//        System.out.println("-------------");
        System.out.println("插入节点以后的树的高度 : "+tree.height());
        System.out.println("-------------");
//        tree.delete(41);
//        tree.delete(75);
//        tree.delete(84);
//        tree.delete(51);

        tree.delete(36);
        tree.delete(41);
        System.out.println("删除节点以后的树的高度 : "+tree.height());

    }
}

参考资料

  • 邓俊辉老师的数据结构课程

猜你喜欢

转载自www.cnblogs.com/Benjious/p/10353733.html