Everlasting Pages

返回

Lab 7:BSTMap

介绍#

在本实验中,你将创建 BSTMap:它是 Map61B 接口的一种基于 BST 的实现,表示一个基本的、基于树的映射。你会完全从零开始创建它,并把所提供的接口当作指南。

完成实现后,你会把自己的实现性能与基于列表的 Map 实现 ULLMap 以及 Java 内置的 TreeMap 类(它也使用 BST)进行比较。

BSTMap#

创建一个名为 BSTMap 的类,使用 BST(二叉搜索树,Binary Search Tree)作为核心数据结构,实现 Map61B 接口。你必须在名为 BSTMap.java 的文件中完成它。除 removeiteratorkeySet 外,你的实现必须实现 Map61B 中给出的所有方法。对于这几个方法,应抛出 UnsupportedOperationException

在创建 BSTMap 类并实现 Map61B 的所有方法之前,你的代码无法编译。你可以一次实现一个方法:先写出所有必需方法的方法签名,但在实现中抛出 UnsupportedOperationException;等轮到实际编写某个方法时,再完成它。

你的 BSTMap 还应额外添加一个 printInOrder() 方法(该方法没有在 Map61B 接口中给出),按 Key 递增顺序打印 BSTMap。我们不会测试这个方法的结果,但你会发现它有助于测试自己的实现!

在实现中,你应当假定 BSTMap<K, V> 中的泛型键 K 扩展了 Comparable。换句话说,你可以假定泛型键 K 具有 compareTo 方法。在 Java 中,可以使用有界类型参数强制满足这一条件。考虑下面这个摘自 Oracle 文档的示例:

我们还建议你使用一个私有的嵌套 BSTNode 类,以帮助完成实现。如何设计和使用这个内部类由你决定!

你可以使用 TestBSTMap.java 测试自己的实现。

下面这些资源可能会有帮助:

  • Lecture 16 的幻灯片
  • 课程资源页面中《Data Structures Into Java》第 109 页和第 111 页的 BST 代码。
  • 可选教材中的 BST 代码。
  • ULLMap.java(已提供):一个能够正常工作的、基于无序链表的 Map61B 实现。

所以……它到底有多快?#

InsertRandomSpeedTest.javaInsertInOrderSpeedTest.java 中提供了两个交互式速度测试。在完成 BSTMap 之前,不要尝试运行这些测试。准备好后,可以在 IntelliJ 中运行它们。

InsertRandomSpeedTest 类会测试你的 BSTMap、已提供的 ULLMap、Java 内置 TreeMap 和 Java 内置 HashMap(你会在下一个实验中进一步探索它)在插入元素时的速度。它会询问用户:要插入的每个 String 所需的长度,以及输入规模(要执行的插入次数)。然后,它会生成指定数量、指定长度的 String,并将它们作为 <String, Integer> 对插入映射中。

尝试运行它,看看随着插入次数增加,你的数据结构与朴素实现和工业级实现相比如何扩展。请记住,在小样本上,渐近分析并不具有代表性;如果看到令人困惑的趋势,请确保输入足够大。把结果记录在名为 speedTestResults.txt 的文件中。结果没有规定的标准格式,也没有规定所需的数据点数量。

现在尝试运行 InsertInOrderSpeedTest。它的行为与 InsertRandomSpeedTest 类似,但这一次,<String, Integer> 键值对中的 String 会按照字典序递增顺序插入。如果你观察到任何有趣现象(希望你观察到了),应当与其他学生和/或 TA 讨论。

可选练习#

这一部分不会评分,但你仍然可以从自动评分器获得反馈。

BSTMap 类中实现 iterator()keySet()remove(K key)remove(K key, V value)。实现 iterator 方法时,应当返回一个遍历键的迭代器。实现 remove() 相当有挑战性。作为额外挑战,请在不使用第二个实例变量存储键集合的情况下实现 keySet()iterator

对于 remove,如果参数键在 BSTMap 中不存在,应返回 null。否则,删除键值对 (key, value) 并返回 value

实验总结与提交#

实验结束时,你的 TA 会讲解参考解答。如果你尚未完成实验,这会很有帮助,因为我们不希望你在实验课之外被这个实验困住太久。(这也是鼓励你参加实验课的一个理由!)

确保提交完成的 BSTMap.javaspeedTestResults.txt,并像往常一样通过 Git 和 Gradescope 提交。

可选渐近分析题#

给定 B——一个包含 N 个键值对的 BSTMap——以及 (K, V)——一个随机键值对——回答下列问题。

除非另有说明,“大 O”界(例如 O(N))和“大 Θ”界(例如 Θ(N))指给定方法调用中的比较次数。

对于第 1–7 题,说明陈述是真还是假。对于第 8 题,给出运行时间界。

  1. B.put(K, V)O(log(N))

  2. B.put(K, V)Θ(log(N))

  3. B.put(K, V)Θ(N)

  4. B.put(K, V)O(N)

  5. B.put(K, V)O(N²)

  6. g(N) 表示:随机调用 B.put(K, V)N 次,随后调用 B.containsKey(K),完成这些操作所需的平均比较次数。那么,g(N) ~ 2(ln(N))

    注意:我们写作 g(N) ~ f(N),表示当 N 变大时,g(N) / f(N) -> 1

  7. 对于键 C != K,同时运行 B.containsKey(K)B.containsKey(C)Ω(log(N))

  8. BSTMap b 由一个 root Node(Key、Value 对)和两个名为 leftrightBSTMap 子树组成。进一步假定,方法 numberOfNodes(BSTMap b) 返回以 b.root 为根的 BSTMap 的节点数;它的运行时间是 Θ(n),其中 n 是以 b 为根的 BSTMap 中的 Node 数量。对于某个正整数 zmystery(b, z) 的运行时间(以大 O 记号表示)是多少?假定 bN 个节点,请给出尽可能紧的界。

你的答案不应包含任何不必要的乘法常数或加法因子。

public Key mystery(BSTMap b, int z) {
    if (z > numberOfNodes(b) || z <= 0)
        return null;
    if (numberOfNodes(b.left) == z-1)
        return b.root.key;
    else if (numberOfNodes(b.left) > z)
        return mystery(b.left, z);
    else
        return mystery(b.right, z-numberOfNodes(b.left) - 1);
}
java

原始页面:https://sp21.datastructur.es/materials/lab/lab7/lab7