数据结构与算法——B+树

B+树 ☆☆☆☆☆

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

  1. 非叶结点仅具有索引作用,也就是说,非叶子结点只存储key,不存储value;
  2. 树的所有叶结点构成一个有序链表,可以按照key排序的次序遍历全部数据。

1 B+树存储数据

若参数M选择为3,那么每个结点最多包含2个键值对,我们以3阶B+树为例,看看B+树的数据存储

插入 C N G A H E K Q 数据为例

image-20200727200208026

2 B树和B+树对比

B+ 树的优点在于:

  • 1.由于B+树在非叶子结点上不包含真正的数据,只当做索引使用,因此在内存相同的情况下,能够存放更多的 key。
  • 2.B+树的叶子结点都是相连的,因此对整棵树的遍历只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。

B树的优点在于:

  • 由于B树的每一个节点都包含key和value,因此我们根据key查找value时,只需要找到key所在的位置,就能找到value,但B+树只有叶子结点存储数据,索引每一次查找,都必须一次一次,一直找到树的最大深度处,也就是叶子结点的深度,才能找到value。

3 代码实现

键值对api设计

类名Entry<K, V>
构造方法public Entry(K k, V v)
成员方法public String toString() // 输出
成员变量public K key; // 键
public V value; // 值

结点类API设计

类名BPlusNode<K extends Comparable, V>
构造方法public BPlusNode(boolean isLeaf)
public BPlusNode(boolean isLeaf, boolean isRoot)
成员方法public V get(K key)// 根据key查找值
public void insertOrUpdate(K key, V value, BPlusTree<K, V> tree)//向指定的树中插入键值对,如果不存在,则插入,如果存在,则更新
private void copy2Nodes(K key, V value, BPlusNode<K, V> left, BPlusNode<K, V> right, BPlusTree<K, V> tree)// 复制原结点到新节点的左右节点
private void updateInsert(BPlusTree<K, V> tree)//插入或更新
private int contains(K key)//判断是否包含指定结点
private void insertOrUpdate(K key, V value)//插入或更新当前键值对
public String toString()
public void printBPlusTree(int index)//输出B+树
成员变量private boolean isLeaf;// 是否为叶子节点
private boolean isRoot; // 是否为根节点
private BPlusNode<K, V> parent; // 父节点
private BPlusNode<K, V> previous; // 叶节点的前节点
private BPlusNode<K, V> next; // 叶节点的后节点
private List<Entry<K, V>> entries; // 节点的关键字列表
private List<BPlusNode<K, V>> children;// 子节点列表

B+树api设计

类名BPlusTree<K extends Comparable, V>
构造方法public BPlusTree(int order)
成员方法public V get(K key)
public void insertOrUpdate(K key, V value)
public void printBPlusTree()
成员变量public BPlusNode<K, V> root;// 根节点
public int order;// 阶数,M值
public BPlusNode<K, V> head; // 叶子节点的链表头
public int height = 0;// 树高

代码实现

package com.jg.tree;

import java.util.ArrayList;
import java.util.List;

/**
 * B+树
 *
 * @Author: 杨德石
 * @Date: 2020/7/27 20:29
 * @Version 1.0
 */
public class BPlusTree<K extends Comparable<K>, V> {

    /**
     * 根节点
     */
    public BPlusNode<K, V> root;

    /**
     * 阶数,M值
     */
    public int order;

    /**
     * 叶子节点的链表头
     */
    public BPlusNode<K, V> head;

    /**
     * 树高
     */
    public int height = 0;

    public BPlusTree(int order) {
        this.order = order;
        root = new BPlusNode<>(true, true);
        head = root;
    }

    public V get(K key) {
        return root.get(key);
    }

    public void insertOrUpdate(K key, V value) {
        root.insertOrUpdate(key, value, this);
    }

    public void printBPlusTree() {
        this.root.printBPlusTree(0);
    }

    private static class BPlusNode<K extends Comparable<K>, V> {

        /**
         * 是否为叶子结点
         */
        private boolean isLeaf;

        /**
         * 是否为根节点
         */
        private boolean isRoot;

        /**
         * 父节点
         */
        private BPlusNode<K, V> parent;
        /**
         * 叶节点的前节点
         */
        private BPlusNode<K, V> previous;
        /**
         * 叶节点的后节点
         */
        private BPlusNode<K, V> next;
        /**
         * 节点的关键字列表
         */
        private List<Entry<K, V>> entries;
        /**
         * 子节点列表
         */
        private List<BPlusNode<K, V>> children;

        public BPlusNode(boolean isLeaf) {
            this.isLeaf = isLeaf;
            entries = new ArrayList<>();
            if (!isLeaf) {
                children = new ArrayList<>();
            }
        }

        public BPlusNode(boolean isLeaf, boolean isRoot) {
            this(isLeaf);
            this.isRoot = isRoot;
        }

        private V get(K key) {
            // 什么情况下需要结束递归?遍历到叶子节点的时候不需要递归
            // 如果是叶子结点
            if (isLeaf) {
                int low = 0;
                int high = entries.size() - 1;
                int mid = 0;
                int comp;
                while (low <= high) {
                    mid = (low + high) / 2;
                    comp = entries.get(mid).key.compareTo(key);
                    if (comp == 0) {
                        return entries.get(mid).value;
                    } else if (comp < 0) {
                        low = mid + 1;
                    } else {
                        high = mid + 1;
                    }
                }
            }

            // 如果传入的key小于节点最左边的key,则沿第一个子节点继续搜索
            if (key.compareTo(entries.get(0).key) < 0) {
                return children.get(0).get(key);
            } else if (key.compareTo(entries.get(entries.size() - 1).key) >= 0) {
                // 如果key大于等于节点最右边的key,沿最后一个子节点继续搜索
                return children.get(children.size() - 1).get(key);
            } else {
                // 否则。沿比key大的第一个子节点继续搜索
                int low = 0;
                int high = entries.size() - 1;
                int mid = 0;
                int comp;
                while (low <= high) {
                    // 计算中间下标
                    mid = (low + high) / 2;
                    // 判断中间结点的的key是否大于给定的key
                    comp = entries.get(mid).key.compareTo(key);
                    if (comp == 0) {
                        // 找着了,但是不一定是叶子结点
                        return children.get(mid + 1).get(key);
                    } else if (comp < 0) {
                        low = mid + 1;
                    } else {
                        high = mid - 1;
                    }
                }
                // 循环结束后,直接从low取出
                return children.get(low).get(key);
            }
        }

        /**
         * 向指定的树中插入键值对,如果不存在,则插入,如果存在,则更新
         *
         * @param key
         * @param value
         * @param tree
         */
        public void insertOrUpdate(K key, V value, BPlusTree<K, V> tree) {
            // 如果是叶子结点
            if (isLeaf) {
                // 不需要分裂,直接插入或更新
                if (contains(key) != -1 || entries.size() < tree.order - 1) {
                    // key存在并且不需要分裂
                    // 直接插入或更新
                    insertOrUpdate(key, value);
                    if (tree.height == 0) {
                        tree.height = 1;
                    }
                    return;
                }
                // 需要分裂
                // 分裂成左右两个节点
                BPlusNode<K, V> left = new BPlusNode<>(true);
                BPlusNode<K, V> right = new BPlusNode<>(true);
                // 设置连接
                if (previous != null) {
                    previous.next = left;
                    left.previous = previous;
                }
                if (next != null) {
                    next.previous = right;
                    right.next = next;
                }
                if (previous == null) {
                    tree.head = left;
                }
                // 构造左右节点的关系
                left.next = right;
                right.previous = left;
                previous = null;
                next = null;
                // 左右节点构造完毕
                // 复制原节点关键字到分裂出来的新节点
                copy2Nodes(key, value, left, right, tree);
                // 如果存在父节点
                if (parent != null) {
                    // 调整子父级关系
                    int index = parent.children.indexOf(this);
                    parent.children.remove(this);
                    left.parent = parent;
                    right.parent = parent;
                    parent.children.add(index, left);
                    parent.children.add(index + 1, right);
                    parent.entries.add(right.entries.get(0));
                    entries = null;
                    children = null;

                    // 从父节点插入或更新
                    parent.updateInsert(tree);
                    parent = null;
                } else {
                    // 没有父节点
                    isRoot = false;
                    BPlusNode<K, V> parent = new BPlusNode<>(false, true);
                    tree.root = parent;
                    left.parent = parent;
                    right.parent = parent;
                    parent.children.add(left);
                    parent.children.add(right);
                    parent.entries.add(right.entries.get(0));
                    entries = null;
                    children = null;
                }
                return;
            }
            // 如果不是叶子结点
            // 如果key小于节点最左边的key,沿第一个子节点继续搜索
            if (key.compareTo(entries.get(0).key) < 0) {
                children.get(0).insertOrUpdate(key, value, tree);
            } else if (key.compareTo(entries.get(entries.size() - 1).key) >= 0) {
                // 如果key大于等于最右边的节点,沿最后一个子节点继续搜索
                children.get(children.size() - 1).insertOrUpdate(key, value, tree);
            } else {
                int low = 0;
                int high = entries.size() - 1;
                int mid = 0;
                int comp;
                while (low <= high) {
                    mid = (low + high) / 2;
                    comp = entries.get(mid).key.compareTo(key);
                    if (comp == 0) {
                        children.get(mid).insertOrUpdate(key, value, tree);
                        break;
                    } else if (comp < 0) {
                        low = mid + 1;
                    } else {
                        high = mid - 1;
                    }
                }
                if (low > high) {
                    children.get(low).insertOrUpdate(key, value, tree);
                }
            }
        }

        /**
         * 复制原结点到新节点的左右节点
         *
         * @param key
         * @param value
         * @param left
         * @param right
         * @param tree
         */
        private void copy2Nodes(K key, V value, BPlusNode<K, V> left, BPlusNode<K, V> right, BPlusTree<K, V> tree) {
            // 记录左节点要复制到的位置
            int leftSize = tree.order / 2;
            boolean b = false;
            for (int i = 0; i < entries.size(); i++) {
                if (leftSize != 0) {
                    leftSize--;
                    if (!b && entries.get(i).key.compareTo(key) > 0) {
                        // 没有被插入,并且当前的key比传入的key大
                        left.entries.add(new Entry<>(key, value));
                        b = true;
                        i--;
                    } else {
                        left.entries.add(entries.get(i));
                    }
                } else {
                    if (!b && entries.get(i).key.compareTo(key) > 0) {
                        // 没有被插入,并且当前的key比传入的key大
                        right.entries.add(new Entry<>(key, value));
                        b = true;
                        i--;
                    } else {
                        right.entries.add(entries.get(i));
                    }
                }
            }
            if (!b) {
                right.entries.add(new Entry<>(key, value));
            }
        }

        /**
         * 插入节点后中间结点的更新
         *
         * @param tree
         */
        private void updateInsert(BPlusTree<K, V> tree) {
            // 如果子节点数超出阶数吗,则需要分裂
            if (children.size() > tree.order) {
                // 分裂成左右两个节点
                BPlusNode<K, V> left = new BPlusNode<>(false);
                BPlusNode<K, V> right = new BPlusNode<>(false);
                // 计算一下分裂后两个节点的长度
                int leftSize = (tree.order + 1) / 2;
                int rightSize = (tree.order + 1) / 2;
                // 复制子节点到分裂出来的新结点,并更新key
                for (int i = 0; i < leftSize; i++) {
                    left.children.add(children.get(i));
                    children.get(i).parent = left;
                }
                for (int i = 0; i < rightSize; i++) {
                    right.children.add(children.get(leftSize + i));
                    children.get(leftSize + i).parent = right;
                }
                for (int i = 0; i < leftSize - 1; i++) {
                    left.entries.add(entries.get(i));
                }
                for (int i = 0; i < rightSize - 1; i++) {
                    right.entries.add(entries.get(leftSize + i));
                }

                if (parent != null) {
                    // 调整子父级关系
                    int index = parent.children.indexOf(this);
                    parent.children.remove(this);
                    left.parent = parent;
                    right.parent = parent;
                    parent.children.add(index, left);
                    parent.children.add(index + 1, right);
                    parent.entries.add(index, entries.get(leftSize - 1));
                    entries = null;
                    children = null;

                    parent.updateInsert(tree);
                    parent = null;
                } else {
                    // 没有父节点
                    // 没有父节点
                    isRoot = false;
                    BPlusNode<K, V> parent = new BPlusNode<>(false, true);
                    tree.root = parent;
                    tree.height = tree.height + 1;
                    left.parent = parent;
                    right.parent = parent;
                    parent.children.add(left);
                    parent.children.add(right);
                    parent.entries.add(right.entries.get(rightSize - 1));
                    entries = null;
                    children = null;
                }
            }
        }

        /**
         * 判断是否包含指定的key
         *
         * @param key
         * @return
         */
        private int contains(K key) {
            int low = 0;
            int high = entries.size() - 1;
            int mid = 0;
            int comp;
            while (low <= high) {
                mid = (low + high) / 2;
                comp = entries.get(mid).key.compareTo(key);
                if (comp == 0) {
                    return mid;
                } else if (comp < 0) {
                    low = mid + 1;
                } else {
                    high = mid - 1;
                }
            }
            return -1;
        }

        /**
         * 插入或更新当前键值对
         *
         * @param key
         * @param value
         */
        private void insertOrUpdate(K key, V value) {
            int low = 0;
            int high = entries.size() - 1;
            int mid = 0;
            int comp;
            while (low <= high) {
                mid = (low + high) / 2;
                comp = entries.get(mid).key.compareTo(key);
                if (comp == 0) {
                    entries.get(mid).value = value;
                } else if (comp < 0) {
                    low = mid + 1;
                } else {
                    high = mid - 1;
                }
            }
            if (low > high) {
                entries.add(low, new Entry<>(key, value));
            }
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("isRoot: ");
            sb.append(isRoot);
            sb.append(", ");
            sb.append("isLeaf: ");
            sb.append(isLeaf);
            sb.append(", ");
            sb.append("keys: ");
            for (Entry<K, V> entry : entries) {
                sb.append(entry.key);
                sb.append(", ");
            }
            sb.append(", ");
            return sb.toString();
        }

        public void printBPlusTree(int index) {
            if (this.isLeaf) {
                System.out.print("层级:" + index + ",叶子节点,keys为: ");
                for (int i = 0; i < entries.size(); ++i) {
                    System.out.print(entries.get(i) + " ");
                }
                System.out.println();
            } else {
                System.out.print("层级:" + index + ",非叶子节点,keys为: ");
                for (int i = 0; i < entries.size(); ++i) {
                    System.out.print(entries.get(i) + " ");
                }
                System.out.println();
                for (int i = 0; i < children.size(); ++i) {
                    children.get(i).printBPlusTree(index + 1);
                }
            }
        }

    }

    private static class Entry<K, V> {
        public K key;
        public V value;

        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public String toString() {
            return key + ":" + value;
        }
    }
}

class Test17 {
    public static void main(String[] args) {
        int order = 3;
        BPlusTree<String, String> tree = new BPlusTree<>(order);
        System.out.println("插入C");
        tree.insertOrUpdate("C", "C");
        System.out.println("插入N");
        tree.insertOrUpdate("N", "N");
        System.out.println("插入G");
        tree.insertOrUpdate("G", "G");
        System.out.println("插入A");
        tree.insertOrUpdate("A", "A");
        System.out.println("插入H");
        tree.insertOrUpdate("H", "H");
        System.out.println("插入E");
        tree.insertOrUpdate("E", "E");
        System.out.println("插入完毕");
        tree.printBPlusTree();
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值