【树】B树与B+树

欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

  • 推荐:kuan 的首页,持续学习,不断总结,共同进步,活到老学到老
  • 导航
    • 檀越剑指大厂系列:全面总结 java 核心技术点,如集合,jvm,并发编程 redis,kafka,Spring,微服务,Netty 等
    • 常用开发工具系列:罗列常用的开发工具,如 IDEA,Mac,Alfred,electerm,Git,typora,apifox 等
    • 数据库系列:详细总结了常用数据库 mysql 技术点,以及工作中遇到的 mysql 问题等
    • 懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作
    • 数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。 ✨✨ 欢迎订阅本专栏 ✨✨

博客目录

    • 一.简单介绍
      • 1.B-树历史
      • 2.B 的含义
      • 3.特性
      • 4.AVL 树高?
      • 5.B 树树高?
    • 二.常见方法
      • 1.定义节点
      • 2.多路查找
      • 3.插入 key 和 child
      • 4.定义树
      • 5.插入
    • 三.核心方法
      • 1.分裂
      • 2.删除
      • 3.完整代码
      • 4.B-树与 2-3 树、2-3-4 树的关系
    • 四.B+树

一.简单介绍

1.B-树历史

B 树(B-Tree)结构是一种高效存储和查询数据的方法,它的历史可以追溯到 1970 年代早期。B 树的发明人 Rudolf Bayer 和 Edward M. McCreight 分别发表了一篇论文介绍了 B 树。这篇论文是 1972 年发表于《ACM Transactions on Database Systems》中的,题目为"Organization and Maintenance of Large Ordered Indexes"。

这篇论文提出了一种能够高效地维护大型有序索引的方法,这种方法的主要思想是将每个节点扩展成多个子节点,以减少查找所需的次数。B 树结构非常适合应用于磁盘等大型存储器的高效操作,被广泛应用于关系数据库和文件系统中。

B 树结构有很多变种和升级版,例如 B+树,B*树和 SB 树等。这些变种和升级版本都基于 B 树的核心思想,通过调整 B 树的参数和结构,提高了 B 树在不同场景下的性能表现。

总的来说,B 树结构是一个非常重要的数据结构,为高效存储和查询大量数据提供了可靠的方法。它的历史可以追溯到上个世纪 70 年代,而且在今天仍然被广泛应用于各种场景。

2.B 的含义

B-树的名称是由其发明者 Rudolf Bayer 提出的。Bayer 和 McCreight 从未解释 B 代表什么,人们提出了许多可能的解释,比如 Boeing、balanced、between、broad、bushy 和 Bayer 等。但 McCreight 表示,越是思考 B-trees 中的 B 代表什么,就越能更好地理解 B-trees

3.特性

一棵 B-树具有以下性质

特性 1:每个节点 x 具有

  • 属性 n,表示节点 x 中 key 的个数
  • 属性 leaf,表示节点是否是叶子节点
  • 节点 key 可以有多个,以升序存储

特性 2:每个非叶子节点中的孩子数是 n + 1、叶子节点没有孩子

特性 3:最小度数 t(节点的孩子数称为度)和节点中键数量的关系如下:

最小度数 t 键数量范围
2 1 ~ 3
3 2 ~ 5
4 3 ~ 7
n (n-1) ~ (2n-1)

其中,当节点中键数量达到其最大值时,即 3、5、7 … 2n-1,需要分裂

特性 4:叶子节点的深度都相同

问:

B-树为什么有最小度数的限制?

答:

B 树中有最小度数的限制是为了保证 B 树的平衡特性。

在 B 树中,每个节点都可以有多个子节点,这使得 B 树可以存储大量的键值,但也带来了一些问题。如果节点的子节点数量太少,那么就可能导致 B 树的高度过高,从而降低了 B 树的效率。此外,如果节点的子节点数量太多,那么就可能导致节点的搜索、插入和删除操作变得复杂和低效。

最小度数的限制通过限制节点的子节点数量,来平衡这些问题。在 B 树中,每个节点的子节点数量都必须在一定的范围内,即 t 到 2t 之间(其中 t 为最小度数)

4.AVL 树高?

100 万的数据使用 avl 树来存储,树高是多少?

AVL 树是一种自平衡二叉搜索树,它的树高(或称为高度)受到节点插入和删除操作的影响,保持在 O(log n) 的水平。在这里,“n” 表示树中节点的数量。

如果你有 100 万个数据要存储在 AVL 树中,树的高度应该接近于 log2(1000000)。计算一下:

log2(1000000) ≈ 19.93

所以,树的高度大约为 20 层(向上取整)。这意味着在 AVL 树中查找、插入和删除操作的时间复杂度通常都在 O(log n) 的范围内,其中 n 是树中的节点数。因此,AVL 树在维护平衡方面非常高效,适合用于需要高效查找和插入的数据结构。

5.B 树树高?

100 万的数据,如果存储到 B-树(最小度数是 500),那么树高大约是多少?

B-树的高度与其最小度数以及存储的关键字数量有关。最小度数表示每个节点最少有多少个子节点。在一个典型的 B-树中,树的高度通常较低,因为它能够容纳更多的关键字在每个节点中。

如果你有 100 万个数据要存储在一个 B-树中,而该 B-树的最小度数为 500,那么我们可以计算大致的树高:

首先,根节点至少包含一个子节点和两个关键字(除非它是叶子节点),然后每个内部节点至少包含 500 个子节点和 501 个关键字。我们可以使用以下方式计算树的高度:

树的高度 = log_500(1000000)

使用换底公式,我们可以将其转换为常用的对数底数,如自然对数或以 2 为底的对数。在这里,我们使用以 2 为底的对数:

树的高度 ≈ log2(1000000) / log2(500)

计算一下:

树的高度 ≈ 19.93 / 8.97 ≈ 2.22

所以,如果将 100 万个数据存储在一个 B-树中,而最小度数为 500,那么树的高度大约是 2.22,但由于树的高度是整数,所以实际树的高度将会是 3。这意味着 B-树的高度较低,使得查找和插入等操作非常高效,适合用于大规模数据的存储和检索。

二.常见方法

1.定义节点

static class Node {
    boolean leaf = true;
    int keyNumber;
    int t;
    int[] keys;
    Node[] children;

    public Node(int t) {
        this.t = t;
        this.keys = new int[2 * t - 1];
        this.children = new Node[2 * t];
    }

    @Override
    public String toString() {
        return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));
    }
}
  • leaf 表示是否为叶子节点
  • keyNumber 为 keys 中有效 key 数目
  • t 为最小度数,它决定了节点中 key 的最小、最大数目,分别是 t-1 和 2t-1
  • keys 存储此节点的 key
  • children 存储此节点的 child
  • toString 只是为了方便调试和测试,非必须

实际 keys 应当改为 entries 以便同时保存 key 和 value,刚开始简化实现

2.多路查找

为上面节点类添加 get 方法

Node get(int key) {
    int i = 0;
    while (i < keyNumber && keys[i] < key) {
        i++;
    }
    if (i < keyNumber && keys[i] == key) {
        return this;
    }
    if (leaf) {
        return null;
    }
    return children[i].get(key);
}

3.插入 key 和 child

为上面节点类添加 insertKey 和 insertChild 方法

void insertKey(int key, int index) {
    System.arraycopy(keys, index, keys, index + 1, keyNumber - index);
    keys[index] = key;
    keyNumber++;
}

void insertChild(Node child, int index) {
    System.arraycopy(children, index, children, index + 1, keyNumber - index);
    children[index] = child;
}

作用是向 keys 数组或 children 数组指定 index 处插入新数据,注意

  • 由于使用了静态数组,并且不会在新增或删除时改变它的大小,因此需要额外的 keyNumber 来指定数组内有效 key 的数目
    • 插入时 keyNumber++
    • 删除时减少 keyNumber 的值即可
  • children 不会单独维护数目,它比 keys 多一个
  • 如果这两个方法同时调用,注意它们的先后顺序,insertChild 后调用,因为它计算复制元素个数时用到了 keyNumber

4.定义树

public class BTree {
    final int t;
    final int MIN_KEY_NUMBER;
    final int MAX_KEY_NUMBER;
    Node root;

    public BTree() {
        this(2);
    }

    public BTree(int t) {
        this.t = t;
        MIN_KEY_NUMBER = t - 1;
        MAX_KEY_NUMBER = 2 * t - 1;
        root = new Node(t);
    }
}

5.插入

public void put(int key) {
    doPut(null, 0, root, key);
}

private void doPut(Node parent, int index, Node node, int key) {
    int i = 0;
    while (i < node.keyNumber && node.keys[i] < key) {
        i++;
    }
    if (i < node.keyNumber && node.keys[i] == key) {
        return;
    }
    if (node.leaf) {
        node.insertKey(key, i);
    } else {
        doPut(node, i, node.children[i], key);
    }
    if (isFull(node)) {
        split(parent, index, node);
    }
}
  • 首先查找本节点中的插入位置 i,如果没有空位(key 被找到),应该走更新的逻辑,目前什么没做
  • 接下来分两种情况
    • 如果节点是叶子节点,可以直接插入了
    • 如果节点是非叶子节点,需要继续在 children[i] 处继续递归插入
  • 无论哪种情况,插入完成后都可能超过节点 keys 数目限制,此时应当执行节点分裂
    • 参数中的 parent 和 index 都是给分裂方法用的,代表当前节点父节点,和分裂节点是第几个孩子

判断依据为:

boolean isFull(Node node) {
    return node.keyNumber == MAX_KEY_NUMBER;
}

三.核心方法

1.分裂

void split(Node parent, int index , Node left) {
    if (parent == null) {
        Node newRoot = new Node(this.t);
        newRoot.leaf = false;
        newRoot.insertChild(root, 0);
        root = newRoot;
        parent = newRoot;
    }
    Node right = new Node(this.t);
    right.leaf = left.leaf;
    right.keyNumber = t - 1;
    System.arraycopy(left.keys, t, right.keys, 0, t - 1);
    if (!left.leaf) {
        System.arraycopy(left.children, t, right.children, 0, t);
    }
    left.keyNumber = t - 1;
    int mid = left.keys[t - 1];
    parent.insertKey(mid, index);
    parent.insertChild(right, index + 1);

}

分两种情况:

  • 如果 parent == null 表示要分裂的是根节点,此时需要创建新根,原来的根节点作为新根的 0 孩子
  • 否则
    • 创建 right 节点(分裂后大于当前 left 节点的),把 t 以后的 key 和 child 都拷贝过去
    • t-1 处的 key 插入到 parent 的 index 处,index 指 left 作为孩子时的索引
    • right 节点作为 parent 的孩子插入到 index + 1 处

2.删除

case 1:当前节点是叶子节点,没找到

case 2:当前节点是叶子节点,找到了

case 3:当前节点是非叶子节点,没找到

case 4:当前节点是非叶子节点,找到了

case 5:删除后 key 数目 < 下限(不平衡)

case 6:根节点

3.完整代码

package com.itheima.algorithm.btree;

import java.util.Arrays;

/**
 * 

B-树

*/
@SuppressWarnings("all") public class BTree { static class Node { int[] keys; // 关键字 Node[] children; // 孩子 int keyNumber; // 有效关键字数目 boolean leaf = true; // 是否是叶子节点 int t; // 最小度数 (最小孩子数) public Node(int t) { // t>=2 this.t = t; this.children = new Node[2 * t]; this.keys = new int[2 * t - 1]; } public Node(int[] keys) { this.keys = keys; } @Override public String toString() { return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber)); } // 多路查找 Node get(int key) { int i = 0; while (i < keyNumber) { if (keys[i] == key) { return this; } if (keys[i] > key) { break; } i++; } // 执行到此时 keys[i]>key 或 i==keyNumber if (leaf) { return null; } // 非叶子情况 return children[i].get(key); } // 向 keys 指定索引处插入 key void insertKey(int key, int index) { System.arraycopy(keys, index, keys, index + 1, keyNumber - index); keys[index] = key; keyNumber++; } // 向 children 指定索引处插入 child void insertChild(Node child, int index) { System.arraycopy(children, index, children, index + 1, keyNumber - index); children[index] = child; } int removeKey(int index) { int t = keys[index]; System.arraycopy(keys, index + 1, keys, index, --keyNumber - index); return t; } int removeLeftmostKey() { return removeKey(0); } int removeRightmostKey() { return removeKey(keyNumber - 1); } Node removeChild(int index) { Node t = children[index]; System.arraycopy(children, index + 1, children, index, keyNumber - index); children[keyNumber] = null; return t; } Node removeLeftmostChild() { return removeChild(0); } Node removeRightmostChild() { return removeChild(keyNumber); } void moveToLeft(Node left) { int start = left.keyNumber; if (!leaf) { for (int i = 0; i <= keyNumber; i++) { left.children[start + i] = children[i]; } } for (int i = 0; i < keyNumber; i++) { left.keys[left.keyNumber++] = keys[i]; } } Node leftSibling(int index) { return index > 0 ? children[index - 1] : null; } Node rightSibling(int index) { return index == keyNumber ? null : children[index + 1]; } } Node root; int t; // 树中节点最小度数 final int MIN_KEY_NUMBER; // 最小key数目 final int MAX_KEY_NUMBER; // 最大key数目 public BTree() { this(2); } public BTree(int t) { this.t = t; root = new Node(t); MAX_KEY_NUMBER = 2 * t - 1; MIN_KEY_NUMBER = t - 1; } // 1. 是否存在 public boolean contains(int key) { return root.get(key) != null; } // 2. 新增 public void put(int key) { doPut(root, key, null, 0); } private void doPut(Node node, int key, Node parent, int index) { int i = 0; while (i < node.keyNumber) { if (node.keys[i] == key) { return; // 更新 } if (node.keys[i] > key) { break; // 找到了插入位置,即为此时的 i } i++; } if (node.leaf) { node.insertKey(key, i); } else { doPut(node.children[i], key, node, i); } if (node.keyNumber == MAX_KEY_NUMBER) { split(node, parent, index); } } /** *

分裂方法

* * @param left 要分裂的节点 * @param parent 分裂节点的父节点 * @param index 分裂节点是第几个孩子 */
void split(Node left, Node parent, int index) { // 分裂的是根节点 if (parent == null) { Node newRoot = new Node(t); newRoot.leaf = false; newRoot.insertChild(left, 0); this.root = newRoot; parent = newRoot; } // 1. 创建 right 节点,把 left 中 t 之后的 key 和 child 移动过去 Node right = new Node(t); right.leaf = left.leaf; System.arraycopy(left.keys, t, right.keys, 0, t - 1); // 分裂节点是非叶子的情况 if (!left.leaf) { System.arraycopy(left.children, t, right.children, 0, t); for (int i = t; i <= left.keyNumber; i++) { left.children[i] = null; } } right.keyNumber = t - 1; left.keyNumber = t - 1; // 2. 中间的 key (t-1 处)插入到父节点 int mid = left.keys[t - 1]; parent.insertKey(mid, index); // 3. right 节点作为父节点的孩子 parent.insertChild(right, index + 1); } // 3. 删除 public void remove(int key) { doRemove(root, key, null, 0); } private void doRemove(Node node, int key, Node parent, int index) { int i = 0; while (i < node.keyNumber) { if (node.keys[i] >= key) { break; } i++; } if (node.leaf) { if (notFound(node, key, i)) { // case 1 return; } node.removeKey(i); // case 2 } else { if (notFound(node, key, i)) { // case 3 doRemove(node.children[i], key, node, i); } else { // case 4 Node s = node.children[i + 1]; while (!s.leaf) { s = s.children[0]; } int k = s.keys[0]; node.keys[i] = k; doRemove(node.children[i + 1], k, node, i + 1); } } if (node.keyNumber < MIN_KEY_NUMBER) { // case 5 balance(node, parent, index); } } private boolean notFound(Node node, int key, int i) { return i >= node.keyNumber || (i < node.keyNumber && node.keys[i] != key); } private void balance(Node node, Node parent, int i) { if (node == root) { if (root.keyNumber == 0 && root.children[0] != null) { root = root.children[0]; } return; } Node leftSibling = parent.leftSibling(i); Node rightSibling = parent.rightSibling(i); if (leftSibling != null && leftSibling.keyNumber > MIN_KEY_NUMBER) { rightRotate(node, leftSibling, parent, i); return; } if (rightSibling != null && rightSibling.keyNumber > MIN_KEY_NUMBER) { leftRotate(node, rightSibling, parent, i); return; } if (leftSibling != null) { mergeToLeft(leftSibling, parent, i - 1); } else { mergeToLeft(node, parent, i); } } private void mergeToLeft(Node left, Node parent, int i) { Node right = parent.removeChild(i + 1); left.insertKey(parent.removeKey(i), left.keyNumber); right.moveToLeft(left); } private void rightRotate(Node node, Node leftSibling, Node parent, int i) { node.insertKey(parent.keys[i - 1], 0); if (!leftSibling.leaf) { node.insertChild(leftSibling.removeRightmostChild(), 0); } parent.keys[i - 1] = leftSibling.removeRightmostKey(); } private void leftRotate(Node node, Node rightSibling, Node parent, int i) { node.insertKey(parent.keys[i], node.keyNumber); if (!rightSibling.leaf) { node.insertChild(rightSibling.removeLeftmostChild(), node.keyNumber + 1); } parent.keys[i] = rightSibling.removeLeftmostKey(); } }

4.B-树与 2-3 树、2-3-4 树的关系

可以这样总结它们之间的关系:

  1. 2-3 树是最小度数为 2 的 B 树,其中每个节点可以包含 2 个或 3 个子节点。
  2. 2-3-4 树是最小度数为 2 的 B 树的一种特殊情况,其中每个节点可以包含 2 个、3 个或 4 个子节点。
  3. B 树是一种更加一般化的平衡树,可以适应不同的应用场景,其节点可以包含任意数量的键值,节点的度数取决于最小度数 t 的设定。

四.B+树

package com.kwan.shuyu.datastructure.btree;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;


/**
 * B+树
 *
 * @author : qinyingjie
 * @version : 2.2.0
 * @date : 2023/9/19 09:52
 */
public class BPlusTree {
    int m;
    InternalNode root;
    LeafNode firstLeaf;

    /*~~~~~~~~~~~~~~~~ HELPER FUNCTIONS ~~~~~~~~~~~~~~~~*/

    /**
     * This method performs a standard binary search on a sorted
     * DictionaryPair[] and returns the index of the dictionary pair
     * with target key t if found. Otherwise, this method returns a negative
     * value.
     *
     * @param dps: list of dictionary pairs sorted by key within leaf node
     * @param t:   target key value of dictionary pair being searched for
     * @return index of the target value if found, else a negative value
     */
    private int binarySearch(DictionaryPair[] dps, int numPairs, int t) {
        Comparator<DictionaryPair> c = (o1, o2) -> {
            Integer a = o1.key;
            Integer b = o2.key;
            return a.compareTo(b);
        };
        return Arrays.binarySearch(dps, 0, numPairs, new DictionaryPair(t, 0), c);
    }

    /**
     * This method starts at the root of the B+ tree and traverses down the
     * tree via key comparisons to the corresponding leaf node that holds 'key'
     * within its dictionary.
     *
     * @param key: the unique key that lies within the dictionary of a LeafNode object
     * @return the LeafNode object that contains the key within its dictionary
     */
    private LeafNode findLeafNode(int key) {

        // Initialize keys and index variable
        Integer[] keys = this.root.keys;
        int i;

        // Find next node on path to appropriate leaf node
        for (i = 0; i < this.root.degree - 1; i++) {
            if (key < keys[i]) {
                break;
            }
        }

		/* Return node if it is a LeafNode object,
		   otherwise repeat the search function a level down */
        Node child = this.root.childPointers[i];
        if (child instanceof LeafNode) {
            return (LeafNode) child;
        } else {
            return findLeafNode((InternalNode) child, key);
        }
    }

    private LeafNode findLeafNode(InternalNode node, int key) {

        // Initialize keys and index variable
        Integer[] keys = node.keys;
        int i;

        // Find next node on path to appropriate leaf node
        for (i = 0; i < node.degree - 1; i++) {
            if (key < keys[i]) {
                break;
            }
        }

		/* Return node if it is a LeafNode object,
		   otherwise repeat the search function a level down */
        Node childNode = node.childPointers[i];
        if (childNode instanceof LeafNode) {
            return (LeafNode) childNode;
        } else {
            return findLeafNode((InternalNode) node.childPointers[i], key);
        }
    }

    /**
     * Given a list of pointers to Node objects, this method returns the index of
     * the pointer that points to the specified 'node' LeafNode object.
     *
     * @param pointers: a list of pointers to Node objects
     * @param node:     a specific pointer to a LeafNode
     * @return (int) index of pointer in list of pointers
     */
    private int findIndexOfPointer(Node[] pointers, LeafNode node) {
        int i;
        for (i = 0; i < pointers.length; i++) {
            if (pointers[i] == node) {
                break;
            }
        }
        return i;
    }

    /**
     * This is a simple method that returns the midpoint (or lower bound
     * depending on the context of the method invocation) of the max degree m of
     * the B+ tree.
     *
     * @return (int) midpoint/lower bound
     */
    private int getMidpoint() {
        return (int) Math.ceil((this.m + 1) / 2.0) - 1;
    }

    /**
     * Given a deficient InternalNode in, this method remedies the deficiency
     * through borrowing and merging.
     *
     * @param in: a deficient InternalNode
     */
    private void handleDeficiency(InternalNode in) {

        InternalNode sibling;
        InternalNode parent = in.parent;

        // Remedy deficient root node
        if (this.root == in) {
            for (int i = 0; i < in.childPointers.length; i++) {
                if (in.childPointers[i] != null) {
                    if (in.childPointers[i] instanceof InternalNode) {
                        this.root = (InternalNode) in.childPointers[i];
                        this.root.parent = null;
                    } else if (in.childPointers[i] instanceof LeafNode) {
                        this.root = null;
                    }
                }
            }
        }

        // Borrow:
        else if (in.leftSibling != null && in.leftSibling.isLendable()) {
            sibling = in.leftSibling;
        } else if (in.rightSibling != null && in.rightSibling.isLendable()) {
            sibling = in.rightSibling;

            // Copy 1 key and pointer from sibling (atm just 1 key)
            int borrowedKey = sibling.keys[0];
            Node pointer = sibling.childPointers[0];

            // Copy root key and pointer into parent
            in.keys[in.degree - 1] = parent.keys[0];
            in.childPointers[in.degree] = pointer;

            // Copy borrowedKey into root
            parent.keys[0] = borrowedKey;

            // Delete key and pointer from sibling
            sibling.removePointer(0);
            Arrays.sort(sibling.keys);
            sibling.removePointer(0);
            shiftDown(in.childPointers, 1);
        }

        // Merge:
        else if (in.leftSibling != null && in.leftSibling.isMergeable()) {

        } else if (in.rightSibling != null && in.rightSibling.isMergeable()) {
            sibling = in.rightSibling;

            // Copy rightmost key in parent to beginning of sibling's keys &
            // delete key from parent
            sibling.keys[sibling.degree - 1] = parent.keys[parent.degree - 2];
            Arrays.sort(sibling.keys, 0, sibling.degree);
            parent.keys[parent.degree - 2] = null;

            // Copy in's child pointer over to sibling's list of child pointers
            for (int i = 0; i < in.childPointers.length; i++) {
                if (in.childPointers[i] != null) {
                    sibling.prependChildPointer(in.childPointers[i]);
                    in.childPointers[i].parent = sibling;
                    in.removePointer(i);
                }
            }

            // Delete child pointer from grandparent to deficient node
            parent.removePointer(in);

            // Remove left sibling
            sibling.leftSibling = in.leftSibling;
        }

        // Handle deficiency a level up if it exists
        if (parent != null && parent.isDeficient()) {
            handleDeficiency(parent);
        }
    }

    /**
     * This is a simple method that determines if the B+ tree is empty or not.
     *
     * @return a boolean indicating if the B+ tree is empty or not
     */
    private boolean isEmpty() {
        return firstLeaf == null;
    }

    /**
     * This method performs a standard linear search on a sorted
     * DictionaryPair[] and returns the index of the first null entry found.
     * Otherwise, this method returns a -1. This method is primarily used in
     * place of binarySearch() when the target t = null.
     *
     * @param dps: list of dictionary pairs sorted by key within leaf node
     * @return index of the target value if found, else -1
     */
    private int linearNullSearch(DictionaryPair[] dps) {
        for (int i = 0; i < dps.length; i++) {
            if (dps[i] == null) {
                return i;
            }
        }
        return -1;
    }

    /**
     * This method performs a standard linear search on a list of Node[] pointers
     * and returns the index of the first null entry found. Otherwise, this
     * method returns a -1. This method is primarily used in place of
     * binarySearch() when the target t = null.
     *
     * @param pointers: list of Node[] pointers
     * @return index of the target value if found, else -1
     */
    private int linearNullSearch(Node[] pointers) {
        for (int i = 0; i < pointers.length; i++) {
            if (pointers[i] == null) {
                return i;
            }
        }
        return -1;
    }

    /**
     * This method is used to shift down a set of pointers that are prepended
     * by null values.
     *
     * @param pointers: the list of pointers that are to be shifted
     * @param amount:   the amount by which the pointers are to be shifted
     */
    private void shiftDown(Node[] pointers, int amount) {
        Node[] newPointers = new Node[this.m + 1];
        for (int i = amount; i < pointers.length; i++) {
            newPointers[i - amount] = pointers[i];
        }
        pointers = newPointers;
    }

    /**
     * This is a specialized sorting method used upon lists of DictionaryPairs
     * that may contain interspersed null values.
     *
     * @param dictionary: a list of DictionaryPair objects
     */
    private void sortDictionary(DictionaryPair[] dictionary) {
        Arrays.sort(dictionary, new Comparator<DictionaryPair>() {
            @Override
            public int compare(DictionaryPair o1, DictionaryPair o2) {
                if (o1 == null && o2 == null) {
                    return 0;
                }
                if (o1 == null) {
                    return 1;
                }
                if (o2 == null) {
                    return -1;
                }
                return o1.compareTo(o2);
            }
        });
    }

    /**
     * This method modifies the InternalNode 'in' by removing all pointers within
     * the childPointers after the specified split. The method returns the removed
     * pointers in a list of their own to be used when constructing a new
     * InternalNode sibling.
     *
     * @param in:    an InternalNode whose childPointers will be split
     * @param split: the index at which the split in the childPointers begins
     * @return a Node[] of the removed pointers
     */
    private Node[] splitChildPointers(InternalNode in, int split) {

        Node[] pointers = in.childPointers;
        Node[] halfPointers = new Node[this.m + 1];

        // Copy half of the values into halfPointers while updating original keys
        for (int i = split + 1; i < pointers.length; i++) {
            halfPointers[i - split - 1] = pointers[i];
            in.removePointer(i);
        }

        return halfPointers;
    }

    /**
     * This method splits a single dictionary into two dictionaries where all
     * dictionaries are of equal length, but each of the resulting dictionaries
     * holds half of the original dictionary's non-null values. This method is
     * primarily used when splitting a node within the B+ tree. The dictionary of
     * the specified LeafNode is modified in place. The method returns the
     * remainder of the DictionaryPairs that are no longer within ln's dictionary.
     *
     * @param ln:    list of DictionaryPairs to be split
     * @param split: the index at which the split occurs
     * @return DictionaryPair[] of the two split dictionaries
     */
    private DictionaryPair[] splitDictionary(LeafNode ln, int split) {

        DictionaryPair[] dictionary = ln.dictionary;

		/* Initialize two dictionaries that each hold half of the original
		   dictionary values */
        DictionaryPair[] halfDict = new DictionaryPair[this.m];

        // Copy half of the values into halfDict
        for (int i = split; i < dictionary.length; i++) {
            halfDict[i - split] = dictionary[i];
            ln.delete(i);
        }

        return halfDict;
    }

    /**
     * When an insertion into the B+ tree causes an overfull node, this method
     * is called to remedy the issue, i.e. to split the overfull node. This method
     * calls the sub-methods of splitKeys() and splitChildPointers() in order to
     * split the overfull node.
     *
     * @param in: an overfull InternalNode that is to be split
     */
    private void splitInternalNode(InternalNode in) {

        // Acquire parent
        InternalNode parent = in.parent;

        // Split keys and pointers in half
        int midpoint = getMidpoint();
        int newParentKey = in.keys[midpoint];
        Integer[] halfKeys = splitKeys(in.keys, midpoint);
        Node[] halfPointers = splitChildPointers(in, midpoint);

        // Change degree of original InternalNode in
        in.degree = linearNullSearch(in.childPointers);

        // Create new sibling internal node and add half of keys and pointers
        InternalNode sibling = new InternalNode(this.m, halfKeys, halfPointers);
        for (Node pointer : halfPointers) {
            if (pointer != null) {
                pointer.parent = sibling;
            }
        }

        // Make internal nodes siblings of one another
        sibling.rightSibling = in.rightSibling;
        if (sibling.rightSibling != null) {
            sibling.rightSibling.leftSibling = sibling;
        }
        in.rightSibling = sibling;
        sibling.leftSibling = in;

        if (parent == null) {

            // Create new root node and add midpoint key and pointers
            Integer[] keys = new Integer[this.m];
            keys[0] = newParentKey;
            InternalNode newRoot = new InternalNode(this.m, keys);
            newRoot.appendChildPointer(in);
            newRoot.appendChildPointer(sibling);
            this.root = newRoot;

            // Add pointers from children to parent
            in.parent = newRoot;
            sibling.parent = newRoot;

        } else {

            // Add key to parent
            parent.keys[parent.degree - 1] = newParentKey;
            Arrays.sort(parent.keys, 0, parent.degree);

            // Set up pointer to new sibling
            int pointerIndex = parent.findIndexOfPointer(in) + 1;
            parent.insertChildPointer(sibling, pointerIndex);
            sibling.parent = parent;
        }
    }

    /**
     * This method modifies a list of Integer-typed objects that represent keys
     * by removing half of the keys and returning them in a separate Integer[].
     * This method is used when splitting an InternalNode object.
     *
     * @param keys:  a list of Integer objects
     * @param split: the index where the split is to occur
     * @return Integer[] of removed keys
     */
    private Integer[] splitKeys(Integer[] keys, int split) {

        Integer[] halfKeys = new Integer[this.m];

        // Remove split-indexed value from keys
        keys[split] = null;

        // Copy half of the values into halfKeys while updating original keys
        for (int i = split + 1; i < keys.length; i++) {
            halfKeys[i - split - 1] = keys[i];
            keys[i] = null;
        }

        return halfKeys;
    }

    /*~~~~~~~~~~~~~~~~ API: DELETE, INSERT, SEARCH ~~~~~~~~~~~~~~~~*/

    /**
     * Given a key, this method will remove the dictionary pair with the
     * corresponding key from the B+ tree.
     *
     * @param key: an integer key that corresponds with an existing dictionary
     *             pair
     */
    public void delete(int key) {
        if (isEmpty()) {

            /* Flow of execution goes here when B+ tree has no dictionary pairs */

            System.err.println("Invalid Delete: The B+ tree is currently empty.");

        } else {

            // Get leaf node and attempt to find index of key to delete
            LeafNode ln = (this.root == null) ? this.firstLeaf : findLeafNode(key);
            int dpIndex = binarySearch(ln.dictionary, ln.numPairs, key);


            if (dpIndex < 0) {

                /* Flow of execution goes here when key is absent in B+ tree */

                System.err.println("Invalid Delete: Key unable to be found.");

            } else {

                // Successfully delete the dictionary pair
                ln.delete(dpIndex);

                // Check for deficiencies
                if (ln.isDeficient()) {

                    LeafNode sibling;
                    InternalNode parent = ln.parent;

                    // Borrow: First, check the left sibling, then the right sibling
                    if (ln.leftSibling != null &&
                            ln.leftSibling.parent == ln.parent &&
                            ln.leftSibling.isLendable()) {

                        sibling = ln.leftSibling;
                        DictionaryPair borrowedDP = sibling.dictionary[sibling.numPairs - 1];

						/* Insert borrowed dictionary pair, sort dictionary,
						   and delete dictionary pair from sibling */
                        ln.insert(borrowedDP);
                        sortDictionary(ln.dictionary);
                        sibling.delete(sibling.numPairs - 1);

                        // Update key in parent if necessary
                        int pointerIndex = findIndexOfPointer(parent.childPointers, ln);
                        if (!(borrowedDP.key >= parent.keys[pointerIndex - 1])) {
                            parent.keys[pointerIndex - 1] = ln.dictionary[0].key;
                        }

                    } else if (ln.rightSibling != null &&
                            ln.rightSibling.parent == ln.parent &&
                            ln.rightSibling.isLendable()) {

                        sibling = ln.rightSibling;
                        DictionaryPair borrowedDP = sibling.dictionary[0];

						/* Insert borrowed dictionary pair, sort dictionary,
					       and delete dictionary pair from sibling */
                        ln.insert(borrowedDP);
                        sibling.delete(0);
                        sortDictionary(sibling.dictionary);

                        // Update key in parent if necessary
                        int pointerIndex = findIndexOfPointer(parent.childPointers, ln);
                        if (!(borrowedDP.key < parent.keys[pointerIndex])) {
                            parent.keys[pointerIndex] = sibling.dictionary[0].key;
                        }

                    }

                    // Merge: First, check the left sibling, then the right sibling
                    else if (ln.leftSibling != null &&
                            ln.leftSibling.parent == ln.parent &&
                            ln.leftSibling.isMergeable()) {

                        sibling = ln.leftSibling;
                        int pointerIndex = findIndexOfPointer(parent.childPointers, ln);

                        // Remove key and child pointer from parent
                        parent.removeKey(pointerIndex - 1);
                        parent.removePointer(ln);

                        // Update sibling pointer
                        sibling.rightSibling = ln.rightSibling;

                        // Check for deficiencies in parent
                        if (parent.isDeficient()) {
                            handleDeficiency(parent);
                        }

                    } else if (ln.rightSibling != null &&
                            ln.rightSibling.parent == ln.parent &&
                            ln.rightSibling.isMergeable()) {

                        sibling = ln.rightSibling;
                        int pointerIndex = findIndexOfPointer(parent.childPointers, ln);

                        // Remove key and child pointer from parent
                        parent.removeKey(pointerIndex);
                        parent.removePointer(pointerIndex);

                        // Update sibling pointer
                        sibling.leftSibling = ln.leftSibling;
                        if (sibling.leftSibling == null) {
                            firstLeaf = sibling;
                        }

                        if (parent.isDeficient()) {
                            handleDeficiency(parent);
                        }
                    }

                } else if (this.root == null && this.firstLeaf.numPairs == 0) {

					/* Flow of execution goes here when the deleted dictionary
					   pair was the only pair within the tree */

                    // Set first leaf as null to indicate B+ tree is empty
                    this.firstLeaf = null;

                } else {

					/* The dictionary of the LeafNode object may need to be
					   sorted after a successful delete */
                    sortDictionary(ln.dictionary);

                }
            }
        }
    }

    /**
     * Given an integer key and floating point value, this method inserts a
     * dictionary pair accordingly into the B+ tree.
     *
     * @param key:   an integer key to be used in the dictionary pair
     * @param value: a floating point number to be used in the dictionary pair
     */
    public void insert(int key, double value) {
        if (isEmpty()) {

            /* Flow of execution goes here only when first insert takes place */

            // Create leaf node as first node in B plus tree (root is null)
            LeafNode ln = new LeafNode(this.m, new DictionaryPair(key, value));

            // Set as first leaf node (can be used later for in-order leaf traversal)
            this.firstLeaf = ln;

        } else {

            // Find leaf node to insert into
            LeafNode ln = (this.root == null) ? this.firstLeaf :
                    findLeafNode(key);

            // Insert into leaf node fails if node becomes overfull
            if (!ln.insert(new DictionaryPair(key, value))) {

                // Sort all the dictionary pairs with the included pair to be inserted
                ln.dictionary[ln.numPairs] = new DictionaryPair(key, value);
                ln.numPairs++;
                sortDictionary(ln.dictionary);

                // Split the sorted pairs into two halves
                int midpoint = getMidpoint();
                DictionaryPair[] halfDict = splitDictionary(ln, midpoint);

                if (ln.parent == null) {

                    /* Flow of execution goes here when there is 1 node in tree */

                    // Create internal node to serve as parent, use dictionary midpoint key
                    Integer[] parent_keys = new Integer[this.m];
                    parent_keys[0] = halfDict[0].key;
                    InternalNode parent = new InternalNode(this.m, parent_keys);
                    ln.parent = parent;
                    parent.appendChildPointer(ln);

                } else {

                    /* Flow of execution goes here when parent exists */

                    // Add new key to parent for proper indexing
                    int newParentKey = halfDict[0].key;
                    ln.parent.keys[ln.parent.degree - 1] = newParentKey;
                    Arrays.sort(ln.parent.keys, 0, ln.parent.degree);
                }

                // Create new LeafNode that holds the other half
                LeafNode newLeafNode = new LeafNode(this.m, halfDict, ln.parent);

                // Update child pointers of parent node
                int pointerIndex = ln.parent.findIndexOfPointer(ln) + 1;
                ln.parent.insertChildPointer(newLeafNode, pointerIndex);

                // Make leaf nodes siblings of one another
                newLeafNode.rightSibling = ln.rightSibling;
                if (newLeafNode.rightSibling != null) {
                    newLeafNode.rightSibling.leftSibling = newLeafNode;
                }
                ln.rightSibling = newLeafNode;
                newLeafNode.leftSibling = ln;

                if (this.root == null) {

                    // Set the root of B+ tree to be the parent
                    this.root = ln.parent;

                } else {

					/* If parent is overfull, repeat the process up the tree,
			   		   until no deficiencies are found */
                    InternalNode in = ln.parent;
                    while (in != null) {
                        if (in.isOverfull()) {
                            splitInternalNode(in);
                        } else {
                            break;
                        }
                        in = in.parent;
                    }
                }
            }
        }
    }

    /**
     * Given a key, this method returns the value associated with the key
     * within a dictionary pair that exists inside the B+ tree.
     *
     * @param key: the key to be searched within the B+ tree
     * @return the floating point value associated with the key within the B+ tree
     */
    public Double search(int key) {

        // If B+ tree is completely empty, simply return null
        if (isEmpty()) {
            return null;
        }

        // Find leaf node that holds the dictionary key
        LeafNode ln = (this.root == null) ? this.firstLeaf : findLeafNode(key);

        // Perform binary search to find index of key within dictionary
        DictionaryPair[] dps = ln.dictionary;
        int index = binarySearch(dps, ln.numPairs, key);

        // If index negative, the key doesn't exist in B+ tree
        if (index < 0) {
            return null;
        } else {
            return dps[index].value;
        }
    }

    /**
     * This method traverses the doubly linked list of the B+ tree and records
     * all values whose associated keys are within the range specified by
     * lowerBound and upperBound.
     *
     * @param lowerBound: (int) the lower bound of the range
     * @param upperBound: (int) the upper bound of the range
     * @return an ArrayList that holds all values of dictionary pairs
     * whose keys are within the specified range
     */
    public ArrayList<Double> search(int lowerBound, int upperBound) {

        // Instantiate Double array to hold values
        ArrayList<Double> values = new ArrayList<Double>();

        // Iterate through the doubly linked list of leaves
        LeafNode currNode = this.firstLeaf;
        while (currNode != null) {

            // Iterate through the dictionary of each node
            DictionaryPair[] dps = currNode.dictionary;
            for (DictionaryPair dp : dps) {

				/* Stop searching the dictionary once a null value is encountered
				   as this the indicates the end of non-null values */
                if (dp == null) {
                    break;
                }

                // Include value if its key fits within the provided range
                if (lowerBound <= dp.key && dp.key <= upperBound) {
                    values.add(dp.value);
                }
            }

			/* Update the current node to be the right sibling,
			   leaf traversal is from left to right */
            currNode = currNode.rightSibling;

        }

        return values;
    }

    /**
     * Constructor
     *
     * @param m: the order (fanout) of the B+ tree
     */
    public BPlusTree(int m) {
        this.m = m;
        this.root = null;
    }

    /**
     * This class represents a general node within the B+ tree and serves as a
     * superclass of InternalNode and LeafNode.
     */
    public class Node {
        InternalNode parent;
    }

    /**
     * This class represents the internal nodes within the B+ tree that traffic
     * all search/insert/delete operations. An internal node only holds keys; it
     * does not hold dictionary pairs.
     */
    private class InternalNode extends Node {
        int maxDegree;
        int minDegree;
        int degree;
        InternalNode leftSibling;
        InternalNode rightSibling;
        Integer[] keys;
        Node[] childPointers;

        /**
         * This method appends 'pointer' to the end of the childPointers
         * instance variable of the InternalNode object. The pointer can point to
         * an InternalNode object or a LeafNode object since the formal
         * parameter specifies a Node object.
         *
         * @param pointer: Node pointer that is to be appended to the
         *                 childPointers list
         */
        private void appendChildPointer(Node pointer) {
            this.childPointers[degree] = pointer;
            this.degree++;
        }

        /**
         * Given a Node pointer, this method will return the index of where the
         * pointer lies within the childPointers instance variable. If the pointer
         * can't be found, the method returns -1.
         *
         * @param pointer: a Node pointer that may lie within the childPointers
         *                 instance variable
         * @return the index of 'pointer' within childPointers, or -1 if
         * 'pointer' can't be found
         */
        private int findIndexOfPointer(Node pointer) {
            for (int i = 0; i < childPointers.length; i++) {
                if (childPointers[i] == pointer) {
                    return i;
                }
            }
            return -1;
        }

        /**
         * Given a pointer to a Node object and an integer index, this method
         * inserts the pointer at the specified index within the childPointers
         * instance variable. As a result of the insert, some pointers may be
         * shifted to the right of the index.
         *
         * @param pointer: the Node pointer to be inserted
         * @param index:   the index at which the insert is to take place
         */
        private void insertChildPointer(Node pointer, int index) {
            for (int i = degree - 1; i >= index; i--) {
                childPointers[i + 1] = childPointers[i];
            }
            this.childPointers[index] = pointer;
            this.degree++;
        }

        /**
         * This simple method determines if the InternalNode is deficient or not.
         * An InternalNode is deficient when its current degree of children falls
         * below the allowed minimum.
         *
         * @return a boolean indicating whether the InternalNode is deficient
         * or not
         */
        private boolean isDeficient() {
            return this.degree < this.minDegree;
        }

        /**
         * This simple method determines if the InternalNode is capable of
         * lending one of its dictionary pairs to a deficient node. An InternalNode
         * can give away a dictionary pair if its current degree is above the
         * specified minimum.
         *
         * @return a boolean indicating whether or not the InternalNode has
         * enough dictionary pairs in order to give one away.
         */
        private boolean isLendable() {
            return this.degree > this.minDegree;
        }

        /**
         * This simple method determines if the InternalNode is capable of being
         * merged with. An InternalNode can be merged with if it has the minimum
         * degree of children.
         *
         * @return a boolean indicating whether or not the InternalNode can be
         * merged with
         */
        private boolean isMergeable() {
            return this.degree == this.minDegree;
        }

        /**
         * This simple method determines if the InternalNode is considered overfull,
         * i.e. the InternalNode object's current degree is one more than the
         * specified maximum.
         *
         * @return a boolean indicating if the InternalNode is overfull
         */
        private boolean isOverfull() {
            return this.degree == maxDegree + 1;
        }

        /**
         * Given a pointer to a Node object, this method inserts the pointer to
         * the beginning of the childPointers instance variable.
         *
         * @param pointer: the Node object to be prepended within childPointers
         */
        private void prependChildPointer(Node pointer) {
            for (int i = degree - 1; i >= 0; i--) {
                childPointers[i + 1] = childPointers[i];
            }
            this.childPointers[0] = pointer;
            this.degree++;
        }

        /**
         * This method sets keys[index] to null. This method is used within the
         * parent of a merging, deficient LeafNode.
         *
         * @param index: the location within keys to be set to null
         */
        private void removeKey(int index) {
            this.keys[index] = null;
        }

        /**
         * This method sets childPointers[index] to null and additionally
         * decrements the current degree of the InternalNode.
         *
         * @param index: the location within childPointers to be set to null
         */
        private void removePointer(int index) {
            this.childPointers[index] = null;
            this.degree--;
        }

        /**
         * This method removes 'pointer' from the childPointers instance
         * variable and decrements the current degree of the InternalNode. The
         * index where the pointer node was assigned is set to null.
         *
         * @param pointer: the Node pointer to be removed from childPointers
         */
        private void removePointer(Node pointer) {
            for (int i = 0; i < childPointers.length; i++) {
                if (childPointers[i] == pointer) {
                    this.childPointers[i] = null;
                }
            }
            this.degree--;
        }

        /**
         * Constructor
         *
         * @param m:    the max degree of the InternalNode
         * @param keys: the list of keys that InternalNode is initialized with
         */
        private InternalNode(int m, Integer[] keys) {
            this.maxDegree = m;
            this.minDegree = (int) Math.ceil(m / 2.0);
            this.degree = 0;
            this.keys = keys;
            this.childPointers = new Node[this.maxDegree + 1];
        }

        /**
         * Constructor
         *
         * @param m:        the max degree of the InternalNode
         * @param keys:     the list of keys that InternalNode is initialized with
         * @param pointers: the list of pointers that InternalNode is initialized with
         */
        private InternalNode(int m, Integer[] keys, Node[] pointers) {
            this.maxDegree = m;
            this.minDegree = (int) Math.ceil(m / 2.0);
            this.degree = linearNullSearch(pointers);
            this.keys = keys;
            this.childPointers = pointers;
        }
    }

    /**
     * This class represents the leaf nodes within the B+ tree that hold
     * dictionary pairs. The leaf node has no children. The leaf node has a
     * minimum and maximum number of dictionary pairs it can hold, as specified
     * by m, the max degree of the B+ tree. The leaf nodes form a doubly linked
     * list that, i.e. each leaf node has a left and right sibling
     */
    public class LeafNode extends Node {
        int maxNumPairs;
        int minNumPairs;
        int numPairs;
        LeafNode leftSibling;
        LeafNode rightSibling;
        DictionaryPair[] dictionary;

        /**
         * Given an index, this method sets the dictionary pair at that index
         * within the dictionary to null.
         *
         * @param index: the location within the dictionary to be set to null
         */
        public void delete(int index) {

            // Delete dictionary pair from leaf
            this.dictionary[index] = null;

            // Decrement numPairs
            numPairs--;
        }

        /**
         * This method attempts to insert a dictionary pair within the dictionary
         * of the LeafNode object. If it succeeds, numPairs increments, the
         * dictionary is sorted, and the boolean true is returned. If the method
         * fails, the boolean false is returned.
         *
         * @param dp: the dictionary pair to be inserted
         * @return a boolean indicating whether or not the insert was successful
         */
        public boolean insert(DictionaryPair dp) {
            if (this.isFull()) {

                /* Flow of execution goes here when numPairs == maxNumPairs */

                return false;
            } else {

                // Insert dictionary pair, increment numPairs, sort dictionary
                this.dictionary[numPairs] = dp;
                numPairs++;
                Arrays.sort(this.dictionary, 0, numPairs);

                return true;
            }
        }

        /**
         * This simple method determines if the LeafNode is deficient, i.e.
         * the numPairs within the LeafNode object is below minNumPairs.
         *
         * @return a boolean indicating whether or not the LeafNode is deficient
         */
        public boolean isDeficient() {
            return numPairs < minNumPairs;
        }

        /**
         * This simple method determines if the LeafNode is full, i.e. the
         * numPairs within the LeafNode is equal to the maximum number of pairs.
         *
         * @return a boolean indicating whether or not the LeafNode is full
         */
        public boolean isFull() {
            return numPairs == maxNumPairs;
        }

        /**
         * This simple method determines if the LeafNode object is capable of
         * lending a dictionary pair to a deficient leaf node. The LeafNode
         * object can lend a dictionary pair if its numPairs is greater than
         * the minimum number of pairs it can hold.
         *
         * @return a boolean indicating whether or not the LeafNode object can
         * give a dictionary pair to a deficient leaf node
         */
        public boolean isLendable() {
            return numPairs > minNumPairs;
        }

        /**
         * This simple method determines if the LeafNode object is capable of
         * being merged with, which occurs when the number of pairs within the
         * LeafNode object is equal to the minimum number of pairs it can hold.
         *
         * @return a boolean indicating whether or not the LeafNode object can
         * be merged with
         */
        public boolean isMergeable() {
            return numPairs == minNumPairs;
        }

        /**
         * Constructor
         *
         * @param m:  order of B+ tree that is used to calculate maxNumPairs and
         *            minNumPairs
         * @param dp: first dictionary pair insert into new node
         */
        public LeafNode(int m, DictionaryPair dp) {
            this.maxNumPairs = m - 1;
            this.minNumPairs = (int) (Math.ceil(m / 2.0) - 1);
            this.dictionary = new DictionaryPair[m];
            this.numPairs = 0;
            this.insert(dp);
        }

        /**
         * Constructor
         *
         * @param dps:    list of DictionaryPair objects to be immediately inserted
         *                into new LeafNode object
         * @param m:      order of B+ tree that is used to calculate maxNumPairs and
         *                minNumPairs
         * @param parent: parent of newly created child LeafNode
         */
        public LeafNode(int m, DictionaryPair[] dps, InternalNode parent) {
            this.maxNumPairs = m - 1;
            this.minNumPairs = (int) (Math.ceil(m / 2) - 1);
            this.dictionary = dps;
            this.numPairs = linearNullSearch(dps);
            this.parent = parent;
        }
    }

    /**
     * This class represents a dictionary pair that is to be contained within the
     * leaf nodes of the B+ tree. The class implements the Comparable interface
     * so that the DictionaryPair objects can be sorted later on.
     */
    public class DictionaryPair implements Comparable<DictionaryPair> {
        int key;
        double value;

        /**
         * Constructor
         *
         * @param key:   the key of the key-value pair
         * @param value: the value of the key-value pair
         */
        public DictionaryPair(int key, double value) {
            this.key = key;
            this.value = value;
        }

        /**
         * This is a method that allows comparisons to take place between
         * DictionaryPair objects in order to sort them later on
         *
         * @param o
         * @return
         */
        @Override
        public int compareTo(DictionaryPair o) {
            if (key == o.key) {
                return 0;
            } else if (key > o.key) {
                return 1;
            } else {
                return -1;
            }
        }
    }

    public static void main(String[] args) {

        // Ensure correct number of arguments
        if (args.length != 1) {
            System.err.println("usage: java BPlusTree ");
            System.exit(-1);
        }

        // Read from file
        String fileName = args[0];
        try {

            // Prepare to read input file
            File file = new File(System.getProperty("user.dir") + "/" + fileName);
            Scanner sc = new Scanner(file);

            // Create output file in which search results will be stored
            FileWriter logger = new FileWriter("output_file.txt", false);
            boolean firstLine = true;

            // Create initial B+ tree
            BPlusTree bpt = null;

            // Perform an operation for each line in the input file
            while (sc.hasNextLine()) {
                String line = sc.nextLine().replace(" ", "");
                String[] tokens = line.split("[(,)]");

                switch (tokens[0]) {

                    // Initializes an m-order B+ tree
                    case "Initialize":
                        bpt = new BPlusTree(Integer.parseInt(tokens[1]));
                        break;

                    // Insert a dictionary pair into the B+ tree
                    case "Insert":
                        bpt.insert(Integer.parseInt(tokens[1]), Double.parseDouble(tokens[2]));
                        break;

                    // Delete a dictionary pair from the B+ tree
                    case "Delete":
                        bpt.delete(Integer.parseInt(tokens[1]));
                        break;

                    // Perform a search or search operation on the B+ tree
                    case "Search":
                        String result = "";

                        // Perform search (across a range) operation
                        if (tokens.length == 3) {
                            ArrayList<Double> values = bpt.search(
                                    Integer.parseInt(tokens[1]),
                                    Integer.parseInt(tokens[2]));

                            // Record search result as a String
                            if (values.size() != 0) {
                                for (double v : values) {
                                    result += v + ", ";
                                }
                                result = result.substring(0, result.length() - 2);
                            } else {
                                result = "Null";
                            }

                        }

                        // Perform search operation
                        else {

							/* Perform search for key, if resulting value is
							   null, then the key could not be found */
                            Double value = bpt.search(Integer.parseInt(tokens[1]));
                            result = (value == null) ? "Null" :
                                    Double.toString(value);
                        }

                        // Output search result in .txt file
                        if (firstLine) {
                            logger.write(result);
                            firstLine = false;
                        } else {
                            logger.write("\n" + result);
                        }
                        logger.flush();

                        break;
                    default:
                        throw new IllegalArgumentException("\"" + tokens[0] +
                                "\"" + " is an unacceptable input.");
                }
            }

            // Close output file
            logger.close();

        } catch (FileNotFoundException e) {
            System.err.println(e);
        } catch (IllegalArgumentException e) {
            System.err.println(e);
        } catch (IOException e) {
            System.err.println(e);
        }
    }
}

觉得有用的话点个赞 呗。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!

如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!

Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!

img

你可能感兴趣的:(s6,算法与数据结构,b树,数据结构)