1 渗透(Percolation)
一个N×N的网格,判断顶部和底部是否连通就是渗透问题。[^1]
如图 1,左侧的网格能渗透,右侧的不能渗透。
2 问题(The problem)
假设每个格子打开(白色)的概率是 p(所以关闭(黑色)的概率是 1-p),那么整个系统是渗透的的概率是多少?当 p 为 0,系统一定是非渗透的;当 p 为 1,系统一定是渗透的。图 2 和图 3 分别表示了当网格为 20*20 和 100*100 时, p 与系统渗透的概率之间的关系。
当 N 足够大的时候,我们会得到一个阈值 p*,当 p < p* 时,NxN 的网格几乎不会渗透;当 p > p* 时,NxN 的网格几乎总是渗透。
还没有数学方法可以推断出阈值 p*,但是我们可以通过计算机程序模拟来估算 p*。
3 渗透问题数据结构(Percolation data type)
1 | public class Percolation { |
3.1 极端案例(Corner cases)
行和列的下标都是从 1 到 n,(1, 1) 是左上角的格子
如果 open()
、isOpen()
、或者isFull()
的参数超出了规定范围,则抛出异常 java.lang.IndexOutOfBoundsException
如果 n ≤ 0,构造函数抛出异常 java.lang.IllegalArgumentException
3.2 性能要求(Performance requirements)
构造函数时间复杂度为 $n^2$ 的正比
所有方法调用并查集的方法 union()
,find()
,connected()
,count()
的次数为常数
4 蒙特卡罗仿真(Monte Carlo simulation)
- 将所有格子初始化为关闭
- 重复以下过程直到系统为渗透的
- 在所有关闭的格子中随机挑选一个
- 打开它
- 估算的渗透阈值为:打开的格子数 / 总格子数
举个例子,下面 4 幅图为 20*20 的网格的估算过程,估算的阈值为 204/400 = 0.51
重复这个过程 T 次,我们可以得到更加精确的渗透阈值。用 $x_t$ 表示第 t 次实验打开的格子数。
$$
\overline{x} = \frac{x_1+x_2+ \cdot\cdot\cdot +x_T}{T}
$$
$$
s^2 = \frac{(x_1-\overline{x})^2+(x_2-\overline{x})^2+ \cdot\cdot\cdot +(x_T-\overline{x})^2}{T-1}
$$
当 T 足够大(至少 30),下面这个式子可以为渗透阈值提供 95% 的置信区间
$$
\left[ \overline{x} - \frac{1.96s}{\sqrt{T}}, \overline{x} + \frac{1.96s}{\sqrt{T}} \right]
$$
为了统计多次实验,我们使用以下数据结构:
1 | public class PercolationStats { |
如果 n ≤ 0 或者 trials ≤ 0,构造函数抛出异常 java.lang.IllegalArgumentException
main()
方法有两个参数:n 和 T。使用 n x n 网格进行 T 次独立实验,打印出样本平均值(sample mean)、样本标准差(sample standard deviation)和 95% 的置信区间。
1 | % java PercolationStats 200 100 |
5 解决方案(Solution)
我们可以把这个问题当做一个动态连通性问题来解决。为了计算方便,我们在网格的顶部和底部各加一个虚拟的节点,这样就把渗透问题简化成判断两个节点能否连通。如图 8所示。
6 代码
见 GitHub 的 repo:PrincetonAlgs4
- Percolation.java(没有解决 backwash 问题)
- PercolationStats.java
7 分析运行时间和占用内存(Analysis of running time and memory usage)
见分析报告
[^1]: 原文见 http://coursera.cs.princeton.edu/algs4/assignments/percolation.html
也可从网站 http://introcs.cs.princeton.edu/java/assignments/ 找到