题目来源http://coursera.cs.princeton.edu/algs4/assignments/percolation.html
作业分为两部分:建立模型和仿真实验。
最关键的部分就是建立模型对象。模型对象要求如下:
The model. We model a percolation system using an n-by-n grid of sites. Each site is either open or blocked. A full site is an open site that can be connected to an open site in the top row via a chain of neighboring (left, right, up, down) open sites. We say the system percolates if there is a full site in the bottom row. In other words, a system percolates if we fill all open sites connected to the top row and that process fills some open site on the bottom row. (For the insulating/metallic materials example, the open sites correspond to metallic materials, so that a system that percolates has a metallic path from top to bottom, with full sites conducting. For the porous substance example, the open sites correspond to empty space through which water might flow, so that a system that percolates lets water fill open sites, flowing from top to bottom.)
边界要求: By convention, the row and column indices are integers between 1 and n, where (1, 1) is the upper-left site: Throw a java.lang.IllegalArgumentException if any argument to open(), isOpen(), or isFull() is outside its prescribed range. The constructor should throw a java.lang.IllegalArgumentException if n ≤ 0.
性能要求: The constructor should take time proportional to n2; all methods should take constant time plus a constant number of calls to the union–find methods union(), find(), connected(), and count().
我的分析
本次作业根据教授在视频课上提示,可以在grid的上方和下方各加入一个虚节点,grid第一行的open节点都与top虚节点连通,grid最后一行的open节点都与bottom虚节点连通。这样只需判断top虚节点与bottom虚节点是否连通就知道grid是否渗透,而不需要去一一选取特定节点比对了。照着这个思路,我实现了下述模型代码。值得注意的是,模型代码的main中测试方法不是仅仅进行各本地测试就可以了,提交作业的时候会进行自动脚本测试,所以提交的版本main方法中必须读取args[0]中的文件名,并加载文件内容进行生成grid和open对应的site。
1 import edu.princeton.cs.algs4.In; 2 import edu.princeton.cs.algs4.StdOut; 3 import edu.princeton.cs.algs4.WeightedQuickUnionUF; 4 /** 5 * @author evasean www.cnblogs.com/evasean/ 6 */ 7 public class Percolation { 8 private static final boolean BLOCK = false; // block state 9 private static final boolean OPEN = true; // open state 10 11 /* topUF bottomUF n 均为final是因为它们只在构造函数时初始化,后续其值未发生变化 */ 12 private final WeightedQuickUnionUF topUF; // 用来记录与top虚节点的连通性 13 private final WeightedQuickUnionUF bottomUF;// 用来记录与bottom虚节点的连通性 14 private final int n; 15 16 private boolean[][] grid; 17 private boolean percolateFlag = false; // grid是否渗透的标志 18 private int openedNum = 0;// 已经open的site数目 19 20 public Percolation(int n) { 21 // create n-by-n grid, with all sites blocked 22 if (n < 1) 23 throw new IllegalArgumentException("grid size should be bigger than one !"); 24 this.n = n; 25 topUF = new WeightedQuickUnionUF(n * n + 1); // 多了一个节点的空间,位置n*n处用来代表虚节点 26 bottomUF = new WeightedQuickUnionUF(n * n + 1); // 多了一个节点的空间,位置n*n处用来代表虚节点 27 grid = new boolean[n][n]; 28 // 初始化grid设为block 29 for (int i = 0; i < n; i++) 30 for (int j = 0; j < n; j++) 31 grid[i][j] = BLOCK; 32 } 33 34 private void validate(int row, int col) { 35 if (row < 1 || col < 1 || row > n || col > n) 36 throw new IllegalArgumentException("input row or col is not illegal!"); 37 } 38 39 public void open(int row, int col) { 40 // open site (row, col) if it is not open already 41 validate(row, col); 42 if (grid[row - 1][col - 1] == OPEN) 43 return; 44 45 grid[row - 1][col - 1] = OPEN; 46 openedNum++; 47 48 // n为1时,open一个节点就达到渗透要求 49 if (n == 1) { 50 topUF.union(0, 1); 51 bottomUF.union(0, 1); 52 percolateFlag = true; 53 return; 54 } 55 56 // 第一行的所有节点都与top虚节点连通 57 if (row == 1) 58 topUF.union(n * n, col - 1); 59 60 // 最后一行的所有节点都与bottom虚节点连通 61 if (row == n) 62 bottomUF.union(n * n, (n - 1) * n + col - 1); 63 64 // 与上方节点的连通性 65 if (row > 1 && grid[row - 2][col - 1] == OPEN) { 66 topUF.union((row - 2) * n + col - 1, (row - 1) * n + col - 1); 67 bottomUF.union((row - 2) * n + col - 1, (row - 1) * n + col - 1); 68 } 69 70 // 与下方节点的连通性 71 if (row < n && grid[row][col - 1] == OPEN) { 72 topUF.union(row * n + col - 1, (row - 1) * n + col - 1); 73 bottomUF.union(row * n + col - 1, (row - 1) * n + col - 1); 74 } 75 76 // 与左侧节点的连通性 77 if (col > 1 && grid[row - 1][col - 2] == OPEN) { 78 topUF.union((row - 1) * n + col - 2, (row - 1) * n + col - 1); 79 bottomUF.union((row - 1) * n + col - 2, (row - 1) * n + col - 1); 80 } 81 82 // 与右侧节点的连通性 83 if (col < n && grid[row - 1][col] == OPEN) { 84 topUF.union((row - 1) * n + col, (row - 1) * n + col - 1); 85 bottomUF.union((row - 1) * n + col, (row - 1) * n + col - 1); 86 } 87 88 /* 89 * 判断条件!percolateFlag是为了防止渗透以后的重复判断 判断条件openedNum>=n 90 * 是因为openedNum达到n时才有可能渗透,在未达到n之前,不需要进行后续判断 91 * 一个节点open的时候刚好使grid渗透的条件是该节点同时与top虚节点和bottom虚节点连通 92 */ 93 if (!percolateFlag && openedNum >= n && topUF.connected(n * n, (row - 1) * n + col - 1) 94 && bottomUF.connected(n * n, (row - 1) * n + col - 1)) 95 percolateFlag = true; 96 97 } 98 99 public boolean isOpen(int row, int col) {100 // is site (row, col) open?101 validate(row, col);102 return grid[row - 1][col - 1] == OPEN;103 }104 105 /**106 * 一个节点只有同时在open状态并且与top虚节点连通时才是full状态107 * @param row108 * @param col109 * @return110 */111 public boolean isFull(int row, int col) {112 // is site (row, col) full?113 validate(row, col);114 if (isOpen(row, col) && topUF.connected(n * n, (row - 1) * n + col - 1))115 return true;116 else117 return false;118 }119 120 public int numberOfOpenSites() {121 // number of open sites122 return openedNum;123 }124 125 public boolean percolates() {126 // does the system percolate?127 return percolateFlag;128 }129 130 //打印一些便于查看的信息131 private void printCheckResult(int row, int col) {132 StdOut.println("p(" + row + "," + col + ") is open=" + isOpen(row, col) + ";is full=" + isFull(row, col)133 + ";percolates=" + percolates());134 }135 136 /**137 * 作业提交时main需要调用该方法,因为提交后在线脚本要用一堆input文件进行测试138 * 139 * @param arg0140 */141 private static void fileInputCheck(String arg0) {142 // test client (optional)143 In in = new In(arg0);//读入input文件名,并加载文件内容144 String s = null;145 int n = -1;146 //读入grid的n147 while (in.hasNextLine()) {148 s = in.readLine();149 if (s != null && !s.trim().equals(""))150 break;151 }152 s = s.trim();153 n = Integer.parseInt(s);154 Percolation p = new Percolation(n);155 156 //读入open的site坐标157 while (in.hasNextLine()) {158 s = in.readLine();159 if (s != null && !s.trim().equals("")) {160 s = s.trim();//去掉输入字符串头尾空格161 String[] sa = s.split("\\s+");//去掉中间所有空格162 if (sa.length != 2)163 break;164 int row = Integer.parseInt(sa[0]);165 int col = Integer.parseInt(sa[1]);166 p.open(row, col);167 }168 }169 170 }171 172 /**173 * 本地测试专用174 */175 private static void generateCheck() {176 // test client (optional)177 Percolation p = new Percolation(3);178 int row = 1, col = 3;179 p.open(row, col);180 p.printCheckResult(row, col);181 row = 2;182 col = 3;183 p.open(row, col);184 p.printCheckResult(row, col);185 row = 3;186 col = 3;187 p.open(row, col);188 p.printCheckResult(row, col);189 row = 3;190 col = 1;191 p.open(row, col);192 p.printCheckResult(row, col);193 row = 2;194 col = 1;195 p.open(row, col);196 p.printCheckResult(row, col);197 row = 1;198 col = 1;199 p.open(row, col);200 p.printCheckResult(row, col);201 }202 203 public static void main(String[] args) {204 //generateCheck();205 fileInputCheck(args[0]);206 }207 }
仿真分析这一部分比较简单,其中需要注意的地方就是“随机选取row和col进行open”,如果简单的用random(int n),选取[0,n)获取row和col,会有很多重复节点被选中,随着n越大,命中率就越低。于是我采用生成一个[0,n*n)的数组,数组内容随机排序,依次读取数组内容,就相当于随机取site。
1 import edu.princeton.cs.algs4.StdOut; 2 import edu.princeton.cs.algs4.StdRandom; 3 import edu.princeton.cs.algs4.StdStats; 4 /** 5 * @author evasean www.cnblogs.com/evasean/ 6 */ 7 public class PercolationStats { 8 /* t fractions 均为final是因为它们只在构造函数时初始化,后续其值未发生变化*/ 9 private final int t;//尝试次数10 private final double[] fractions;//每一次尝试的渗透率得分11 12 private double mean;13 private double stddev;14 15 public PercolationStats(int n, int trials) {16 // perform trials independent experiments on an n-by-n grid17 if (n <= 0 || trials <= 0)18 throw new IllegalArgumentException("n ≤ 0 or trials ≤ 0");19 t = trials;20 fractions = new double[t];21 for (int i = 0; i < t; i++) { //t次尝试22 Percolation p = new Percolation(n);23 int openNum = 0;24 //为了实现随机open一个site,模仿QuickUnion的定位方法25 //先生成一个[0,n*n)的数组,数组内容随机排序,依次读取数组内容,就相当于随机取site26 int[] rand = StdRandom.permutation(n * n);27 for (int pos : rand) {28 //pos = (row-1)*n + col -129 int row = pos / n + 1; 30 int col = pos % n + 1;31 p.open(row, col);32 openNum++;33 //只有openNum>=n时才有判断是否渗透的必要34 if (openNum >= n && p.percolates())35 break;36 }37 double pt = (double) openNum / (n * n);//单次尝试的渗透率38 fractions[i] = pt;39 }40 /* 作业提交时的某个测试案例要求mean()、stddev()、confidenceLo()、confidenceHi()41 * 在任何时候任何次序调用的情况下都必须返回相同的值,故需要在构造函数中计算mean和stddev42 */43 //作业提交时的某个测试案例要调用一次StdStats.mean方法44 mean = StdStats.mean(fractions);45 //作业提交时的某个测试案例要求要调用一次StdStats.stddev方法46 stddev = StdStats.stddev(fractions);47 }48 49 public double mean() {50 // sample mean of percolation threshold51 return mean;52 }53 54 public double stddev() {55 // sample standard deviation of percolation threshold56 return stddev;57 }58 59 public double confidenceLo() {60 // low endpoint of 95% confidence interval61 return mean - 1.96 * stddev / Math.sqrt(t);62 }63 64 public double confidenceHi() {65 // high endpoint of 95% confidence interval66 return mean + 1.96 * stddev / Math.sqrt(t);67 }68 69 public static void main(String[] args) {70 // test client (described below)71 int n = Integer.parseInt(args[0]);72 int t = Integer.parseInt(args[1]);73 PercolationStats ps = new PercolationStats(n, t);74 StdOut.printf("%-25s %s %f \n", "means", "=", ps.mean());75 StdOut.printf("%-25s %s %f \n", "stddev", "=", ps.stddev());76 StdOut.printf("%-25s %s%f%s%f%s\n", "95% confidence interval", "= [", ps.confidenceLo(), ", ",77 ps.confidenceHi(), "]");78 }79 }