跳转到主要内容 链接 搜索 菜单 展开 文档 (外部链接)

作业2:渗透

截止日期:2024年2月26日

与之前的作业不同,本次作业的长度相当于一个项目。请尽早开始。特此提醒。对于 未能理解本次作业的深度而导致的延期申请,我们将不予批准

常见问题

每次作业顶部都会链接一个常见问题页面。你也可以通过在URL末尾添加“/faq”来访问。作业2的常见问题位于 这里

获取骨架文件

请按照 作业工作流程指南 开始本次作业。本次作业的代号是 hw2

简介

在本程序中,我们将编写一个程序,通过 蒙特卡洛模拟 来估算渗透阈值。

介绍视频

本次作业的介绍视频可以在 此链接 找到。视频分为三个部分:简介、实现提示和优化提示。如果你想迎接更难的挑战,可以忽略这些提示。如果你想观看Hug教授在普林斯顿大学时制作的八年前的视频,请参见 此链接 。注意,这些视频提到了实现 PercolationStats.java ;我们现在已经提供了这个类的完整解决方案,所以你不需要担心这一步。

作业2幻灯片

本次作业的幻灯片可以在 这里 找到。因为这是作业而不是项目,我们会提供一些解题思路提示。如果你想迎接更大的挑战,可以忽略它们。

为什么重要?

给定一个由随机分布的绝缘材料和金属材料组成的复合系统:需要多大比例的金属材料才能使复合系统成为电导体?给定一个表面有水(或地下有石油)的多孔地貌,在什么条件下水能够渗透到底部(或石油能喷涌到表面)?科学家们定义了一个称为渗透的抽象过程来模拟这种情况。

模型

我们使用N×N的网格站点来模拟渗透系统。每个站点要么是开放的,要么是阻塞的。 充满站点 是指一个开放站点,它可以通过相邻(左、右、上、下)的开放站点链连接到顶部一行的开放站点。如果底部一行有一个充满站点,我们就说系统发生了渗透。换句话说,如果我们填充所有与顶部行相连的开放站点,并且这个过程填充了底部行的某个开放站点,那么系统就发生了渗透。(对于绝缘/金属材料的例子,开放站点对应于金属材料,因此发生渗透的系统有一条从顶部到底部的金属路径,充满站点是导电的。对于多孔物质的例子,开放站点对应于水可以流过的空隙,因此发生渗透的系统让水填充开放站点,从顶部流到底部。)

在下图中,你可以看到在左侧的系统中,水能够从顶部一行的一个站点开始,通过空的站点向下渗透,直到底部一行的一个空站点。

而在右侧,顶部行站点中的水无法向下渗透到底部行的开放站点。

渗透

Percolation.java

Percolation 类

为了模拟渗透系统,请使用以下API完成 Percolation 类:

public class Percolation {
   public Percolation(int N)                // create N-by-N grid, with all sites initially blocked
   public void open(int row, int col)       // open the site (row, col) if it 是 not open already
   public boolean 是Open(int row, int col)  // 是 the site (row, col) open?
   public boolean 是Full(int row, int col)  // 是 the site (row, col) full?
   public int numberOfOpenSites()           // number of open sites
   public boolean percolates()              // does the system percolate?
}

渗透问题

在一个著名的科学问题中,研究人员对以下问题感兴趣:如果站点以概率$p$独立地设置为开放(因此以概率$1-p$阻塞),系统发生渗透的概率是多少?当$p$等于0(没有站点开放)时,系统不会渗透;当$p$等于1(所有站点都开放)时,系统会渗透。下图显示了20×20随机网格(左)和100×100随机网格(右)的站点空置概率$p$与渗透概率的关系。

阈值20 阈值100

当$N$足够大时,存在一个阈值$p^*$,当$p < p^*$时,随机N×N网格几乎从不渗透;当$p > p^*$时,随机N×N网格几乎总是渗透。目前还没有推导出确定渗透阈值$p^*$的数学解。你的任务是编写一个计算机程序来估算$p^*$。

通过实现上述API中给出的所有方法,完成 Percolation.java

WeightedQuickUnionUF

WeightedQuickUnionUF 类由普林斯顿大学的 algs4 库提供,具有以下API:

public class WeightedQuickUnionUF {
   public WeightedQuickUnionUF(int n)      // creates a UF with n elements
   public int count()                      // number of disjoint sets
   public int find(int p)                  // the root of p's set
   public boolean connected(int p, int q)  // whether p and q are in the same set
   public void union(int p, int q)         // join p and q's sets together
}

你的代码必须使用 WeightedQuickUnionUF 类! 它已经为你实现好了,所以不要重新实现并查集抽象数据类型。

提示与边界情况

重要 :我们已经发布了一系列视频,包含一些如何完成本次作业的提示!请务必观看;它们可能会为你节省大量时间。

边界情况

按照惯例,行和列的索引是0到 N - 1 之间的整数,其中(0, 0)是左上角的站点:如果 open() 是Open() 是Full() 的任何参数超出其规定范围,则抛出 java.lang.IndexOutOfBoundsException 。如果 N ≤ 0,构造函数应抛出 java.lang.IllegalArgumentException

性能要求

构造函数的时间复杂度应与$N^2$成正比;所有方法应在常数时间内完成,加上对并查集方法 union() find() connected() count() 的常数次调用。满足这些要求有点棘手!你可以先创建一个能正常工作的解决方案,然后再想办法让它更快。关于满足速度要求的提示,请参阅本规范开头的 视频 。你的 numberOfOpenSites() 方法必须在常数时间内完成。本作业的部分目标是学习如何将一个问题(渗透)转化为已解决问题(不相交集,即并查集)的形式。

如果你读到规范的这一部分仍然不知道如何开始,请阅读这些 幻灯片 并观看这个 视频

PercolationStats.java

蒙特卡洛模拟。 为了估算渗透阈值,请考虑以下计算实验:

  • 将所有站点初始化为阻塞状态。
  • 重复以下操作,直到系统发生渗透:
    • 在所有阻塞站点中随机均匀地选择一个站点。
    • 打开该站点。
  • 系统发生渗透时已打开站点的比例提供了渗透阈值的估计值。

例如,如果按照下面的快照在20×20的网格中打开站点,那么我们对渗透阈值的估计是204/400 = 0.51,因为当第204个站点被打开时系统发生渗透。这些图像分别对应于50、100、150和204个站点被打开的情况。

渗透50 渗透100 渗透150 渗透204

如果你对以下计算背后的数学原理感兴趣,请点击这里。

通过重复这个计算实验$T$次并取平均值,我们可以获得更准确的渗透阈值估计。设$x_t$为第$t$次计算实验中开放站点的比例。样本均值$\mu$提供了渗透阈值的估计值;样本标准差$\sigma$衡量了阈值的锐度。

$\mu = \frac{x_1 + x_2 + … + x_T}{T}$,$\sigma^2 = \frac{(x_1 - \mu)^2 + (x_2 - \mu)^2 + … + (x_T - \mu) ^2}{T-1}$

假设$T$足够大(例如,至少30),以下提供了渗透阈值的95%置信区间:

$[\mu - \frac{1.96\sigma}{\sqrt{T}}, \mu + \frac{1.96\sigma}{\sqrt{T}}]$

为了执行一系列计算实验,我们为你提供了一个 PercolationStats 数据类型。

构造函数接受两个参数 N T ,并在 N × N 网格上执行 T 次独立的计算实验(如上所述)。利用这些实验数据,它计算出渗透阈值的均值、标准差和95%置信区间。

打开 PercolationStats.java ,查看提供的构造函数和方法。然后运行 main 方法,并解释结果。这些数字告诉你关于渗透问题的解决方案的什么信息?

这部分作业没有交付物;如果你正确实现了 Percolation.java ,你现在应该有了渗透阈值 p 的95%置信区间。

运行时分析(不评分)

这部分作业不会评分,但强烈建议你至少阅读并思考以下几点:

  • 使用 QuickFindUF 中的快速查找算法实现 Percolation 数据类型。使用 Stopwatch 来测量不同 N T 值下 PercolationStats 的总运行时间。将 N 加倍对总运行时间有什么影响?将 T 加倍对总运行时间有什么影响?

  • 现在,使用 WeightedQuickUnionUF 中的加权快速合并算法实现 Percolation 数据类型。回答与上一要点相同的问题。

提供的文件

我们提供两个客户端程序作为大规模可视化跟踪工具。 我们强烈建议使用它们来测试和调试你的 Percolation 实现。

示例数据文件

inputFiles 目录包含一些用于可视化客户端的示例文件,包括我们将在下面使用的 input20.txt

这里 是每个输入文件对应的预期输出图像。

基于文件的可视化客户端

PercolationPicture.java 通过执行以下步骤进行可视化:

  • 从文件中读取网格大小N。
  • 创建一个N×N的站点网格(初始时全部阻塞)。
  • 从文件中读取要打开的站点序列(第i行,第j列)。每打开一个站点后,使用标准绘图将充满站点绘制成浅蓝色,开放站点(未充满的)绘制成白色,阻塞站点绘制成黑色,站点(0, 0)位于左上角。

对于输入文件 input20.txt ,程序应产生如下图所示的输出。这些图像分别对应于50、100、150、204和250个站点被打开的情况。

渗透50 渗透100 渗透150

渗透204 渗透250

你可以通过在IntelliJ程序的 运行 -> 编辑配置 -> + -> 应用程序 选项卡中传入正确的参数来运行此输入的可视化工具。在这里,将"主类"设置为 PercolationPicture ,将"程序参数"设置为输入文件(例如, inputFiles/input20.txt )。最后,点击"运行"按钮开始运行可视化工具。

打开 PercolationPicture.java 并按照上述步骤运行可视化工具。使用此工具帮助你调试 Percolation.java 方法!

交互式可视化客户端

InteractivePercolationVisualizer.java 使用鼠标作为输入,对渗透系统中打开站点的结果进行动画展示。它接受一个命令行整数 N 来指定网格大小。作为额外功能,它会按照 PercolationPicture (如上所述)使用的相同格式打印出打开的站点序列,因此你可以用它来准备有趣的测试文件。如果你设计了一个有趣的数据文件,欢迎在Ed上分享。

打开 InteractivePercolationVisualizer.java ,并按照与 PercolationPicture 相同的步骤运行交互式可视化工具。注意,对于交互式版本,你 不需要 提供程序参数。

使用此工具帮助你调试 Percolation.java 方法!

注意:如果你使用的是IntelliJ,可以在 运行 -> 编辑配置 中选择要运行的类(即选择你想要运行的main方法)。更多信息,请查看常见问题

测试

你会发现 InteractivePercolationVisualizer 作为验证 Percolation 类是否正确实现的完整性检查非常有用。如果你的实现正确,点击单元格应该会打开(并在必要时填充)这些站点。还会显示开放站点的数量以及系统是否发生渗透的提示。

tests 文件夹中,我们为你提供了两个基本测试。 你应该向 PercolationTest.java 添加更多测试来测试你的代码。

提交与交付物

你应该按照通常的方式提交,即推送到GitHub,然后在Gradescope上提交。本次作业没有速度限制。

交付物:

  • Percolation.java

致谢

本作业最初由普林斯顿大学的Kevin Wayne和Bob Sedgewick开发,自动评分器由Josh Hug为普林斯顿算法课程构建。