Algorithms Part1課程第一周的Programming Assignment是Percolation問題,該問題是Union-Find算法的一個應(yīng)用實例。作業(yè)要求見以下鏈接:http://coursera.cs.princeton.edu/algs4/assignments/percolation.html,要求用Java實現(xiàn)算法。此外該課程使用了作者提供的API用以簡化編程,如基本輸入、輸出、隨機數(shù)生成、數(shù)據(jù)分析等庫函數(shù),在上述鏈接中可直接下載。
模型描述:
Percolation即滲透過程,其模型如下:一個方形“水槽”(system)由N*N個方格(site)組成,每個方格有開啟(open)或關(guān)閉(blocked)兩個狀態(tài),相鄰(即上下左右)的開啟格子能夠構(gòu)成一條同路。如果一個格子處于開啟狀態(tài),并且與頂端的一行能夠通過開啟的格子實現(xiàn)連通(connected)通路,我們說這個格子處于充滿水(full)的狀態(tài);如果一個“水槽”(system)的頂端格子與底部格子通過開啟的格子實現(xiàn)了連通,我們稱這個“水槽”是可以滲透(percolates)的。如下圖所示:

第一章的作業(yè)需要制作兩個類。第一個Percolation包含五個方法,主要是用來打開滲漏的格子。第二個類PercolationStats 同樣有五個方法,用來找出N*N格子中,能夠產(chǎn)生滲漏的閾值概率的95%置信區(qū)間。先說一下大概的想法和原理。官網(wǎng)上提供有這道題目的思路,就是在創(chuàng)建格子的過程中首先要在頂層和底層各加一層,這樣的話在測試是否滲漏時直接測試(0,1)和(N+1,1)就好了首先來說下第一個類:官網(wǎng)的API已經(jīng)寫好:
public class Percolation {
public Percolation(int N) // create N-by-N grid, with all sites blocked
public void open(int i, int j) // open site (row i, column j) if it is not open already
public boolean isOpen(int i, int j) // is site (row i, column j) open?
public boolean isFull(int i, int j) // is site (row i, column j) full?
public boolean percolates() // does the system percolate?
public static void main(String[] args // test client (optional)
}
- 構(gòu)造函數(shù) Percolation
該構(gòu)造方法對模型初始化,我們創(chuàng)建一個NN大小的一維布爾數(shù)組作為“水槽”,數(shù)組值記錄每個格子的開閉狀態(tài),初始狀態(tài)為關(guān)閉。此外,我們需要另一個(NN+2)大小的一維數(shù)組存儲連通關(guān)系,根據(jù)Union-Find算法的學(xué)習(xí),我們創(chuàng)建類型為WeightedQuickUnionUF的數(shù)組。大小之所以為(N*N+2),是因為在檢查是否滲透的時候,可以通過在頂部與底部設(shè)置兩個虛擬點,其與頂端或底部的一排是相連通的,這樣檢查是否滲透的時候,無需遍歷頂部或底部一排的每一個點,只需要檢查兩個虛擬點是否連通即可,如下圖所示:
image.png - 打開格子方法open
檢查坐標(biāo)為(i, j)的點是否開啟,如果沒有開啟,則將其開啟。注意i,j的取值范圍為1~N。此外,開啟格子后,需要檢查其上下左右的格子是否同樣已經(jīng)打開,如果已經(jīng)打開,則通過WeightedQuickUnionUF的union()方法將兩個格子連通起來;另外根據(jù)作業(yè)要求,需要首先檢查是否越界,拋出java.lang.IndexOutOfBoundsException()錯誤; - isOpen(int i, int j):只需要返回該點的開閉狀態(tài)值;
- isFull(int i, int j):檢查該點是否已經(jīng)打開并且已經(jīng)與頂部的虛擬點(相當(dāng)于頂部的一排)連通,用WeightedQuickUnionUF的connected()方法檢測;
- percolates():檢查頂部的虛擬點與底部的虛擬點是否連通,用WeightedQuickUnionUF的connected()方法檢測;
注意:
- 加入虛擬節(jié)點。首虛擬節(jié)點與第一層所有打開的元素相關(guān)聯(lián),尾虛擬節(jié)點和最后一層所有打開的節(jié)點相關(guān)聯(lián)。如果首尾虛擬節(jié)點在一個集合中,那么系統(tǒng)是滲透成功的。 但是也會出現(xiàn)一個問題(backwash),因為有一些格子雖然從上面不能滲透到,但和尾虛擬節(jié)點連接后,他們也和首虛擬節(jié)點在一個集合了。
- 使用兩個并查集避免backwash。一個只負(fù)責(zé)維護首虛擬節(jié)點,當(dāng)進行isFull判斷是否只考慮這個并查集,另一個并查集進行首尾虛擬節(jié)點的維護,用在percolates的判斷中。

- 在用一維數(shù)組模擬二維“水槽”時,我們用i,j坐標(biāo)進行表示,(i, j)表示的是數(shù)組中序號為(i - 1)*N + (j - 1)的元素;此外,由于虛擬點的設(shè)置,坐標(biāo)的對應(yīng)容易出現(xiàn)混亂,需要首先考慮清楚這個問題。所以我們將數(shù)組的N初始化為N+1
package percolation; /**
* @author rxy8023
* @version 1.0 2017/6/17 22:00
*/
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.WeightedQuickUnionUF;
public class Percolation {
// number of grid should be n +1,because row and column indices are between 1 and n
private int n;
// union-find data structure
private WeightedQuickUnionUF uf;
// mark open site
// 0 for Blocked site, 1 for Open site, 2 for Open site connected to the bottom
private byte[][] open;
// number of open sites
private int num;
/**
* create n-by-n grid, with all sites blocked.
*
* @param n number of dimension
*/
public Percolation(int n) {
if (n <= 0) throw new IllegalArgumentException("Invalid input : n must > 0 !");
this.n = n + 1;
uf = new WeightedQuickUnionUF(this.n * this.n);
open = new byte[this.n][this.n];
}
public static void main(String[] args) {
In in = new In(args[0]);
int n = in.readInt();
Percolation percolation = new Percolation(n);
boolean isPercolated = false;
int count = 0;
while (!in.isEmpty()) {
int row = in.readInt();
int col = in.readInt();
if (!percolation.isOpen(row, col)) {
count++;
}
percolation.open(row, col);
isPercolated = percolation.percolates();
if (isPercolated) {
break;
}
}
StdOut.println(count + " open sites");
if (isPercolated) {
StdOut.println("percolates");
} else {
StdOut.println("does not percolate");
}
}
/**
* Validate the row and col indices.
*
* @param row row index
* @param col col index
*/
private void validate(int row, int col) {
if (row <= 0 || row >= n) {
throw new IndexOutOfBoundsException("Invalid input : row index out of bounds !");
}
if (col <= 0 || row >= n) {
throw new IndexOutOfBoundsException("Invalid input : col index out of bounds !");
}
}
/**
* open site (row, col) if it is not open already
*
* @param row the index of row
* @param col the index of column
*/
public void open(int row, int col) {
validate(row, col);
// is open already
if (isOpen(row, col)) return;
// make this site open
open[row][col] = 1;
num++;
// we make 0 represent the virtual-top, 1 represent the virtual-bottom.
// is the bottom row
if (row == n -1) open[row][col] = 2;
// is the top row
if (row == 1) {
uf.union(0, row * n + col);
// 1-by-1 grid corner case
if (open[row][col] == 2) open[0][0] = 2;
}
// above site is open
if (row - 1 > 0 && isOpen(row - 1, col)) {
update(row - 1, col, row, col);
}
// below site is open
if (row + 1 < n && isOpen(row + 1, col)) {
update(row + 1, col, row, col);
}
// left site is open
if (col - 1 > 0 && isOpen(row, col - 1)) {
update(row, col - 1, row, col);
}
// right site is open
if (col + 1 < n && isOpen(row, col + 1)) {
update(row, col + 1, row, col);
}
}
/**
* update components: connect the opened site to all of its adjacent open sites
*
* @param i1 adjacent open site row index
* @param j1 adjacent open site col index
* @param i2 the opened site row index
* @param j2 the opened site col index
*/
private void update(int i1, int j1, int i2, int j2) {
int p = uf.find(i1 * n + j1);
int q = uf.find(i2 * n + j2);
uf.union(i1 * n + j1, i2 * n + j2);
// if one of them is connected to bottom, then the updated component is connected to bottom too.
if (open[p / n][p % n] == 2 || open[q / n][q % n] == 2) {
int t = uf.find(i2 * n + j2);
open[t / n][t % n] = 2;
}
}
/**
* is site (row, col) open?
*
* @param row row index
* @param col col index
* @return
*/
public boolean isOpen(int row, int col) {
validate(row, col);
return open[row][col] > 0;
}
/**
* is site (row, col) full?
*
* @param row row index
* @param col col index
* @return
*/
public boolean isFull(int row, int col) {
validate(row, col);
return open[row][col] > 0 && uf.connected(0, row * n + col);
}
/**
* number of open sites
*
* @return
*/
public int numberOfOpenSites() {
return num;
}
/**
* does the system percolate?
*
* @return
*/
public boolean percolates() {
int root = uf.find(0);
return open[root / n][root % n] == 2;
}
}
蒙特卡洛仿真分析(PercolationStats類)
用蒙特卡洛(Monte Carlo)方法進行仿真分析,確定滲透閾值p,具體方法是:
初始化將所有N*N個格子置于關(guān)閉狀態(tài);
每次隨機選擇一個格子進行開啟操作,直到系統(tǒng)達到滲透狀態(tài);
統(tǒng)計此時開啟的格子數(shù),與全部格子數(shù)相比計算一個p值的樣本;
獨立重復(fù)T次上述實驗,最后計算出T個p值的樣本,記為x1,x2……xT。
進行以下統(tǒng)計操作。
按照下式計算均值與方差:
假設(shè)T值足夠大(至少30次),則可以根據(jù)下式計算置信區(qū)間在95%的的滲透閾值范圍:
package percolation;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdRandom;
import edu.princeton.cs.algs4.StdStats;
/**
* @author rxy8023
* @version 1.0 2017/6/17 22:00
*/
public class PercolationStats {
// sample mean of percolation threshold
private double mean;
// sample standard deviation of percolation threshold
private double stddev;
// low endpoint of 95% confidence interval
private double confidenceLo;
// high endpoint of 95% confidence interval
private double confidenceHi;
// est[i] = estimate of percolation threshold in perc[i]
private double[] est;
/**
* perform trials independent experiments on an n-by-n grid
*
* @param n
* @param trials
*/
public PercolationStats(int n, int trials) {
if (n <= 0 || trials <= 0) throw new IllegalArgumentException("Invalid input : n or trials musu > 0 !");
est = new double[trials];
for (int k = 0; k < trials; k++) {
Percolation perc = new Percolation(n);
double count = 0;
while (!perc.percolates()) {
int i = StdRandom.uniform(1, n + 1);
int j = StdRandom.uniform(1, n + 1);
if (perc.isOpen(i, j)) continue;
perc.open(i, j);
count++;
}
est[k] = count / (n * n);
}
mean = StdStats.mean(est);
stddev = StdStats.stddev(est);
confidenceLo = mean - (1.96 * stddev) / Math.sqrt(trials);
confidenceHi = mean + (1.96 * stddev) / Math.sqrt(trials);
}
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
int t = Integer.parseInt(args[1]);
PercolationStats stats = new PercolationStats(n, t);
StdOut.println("mean = " + stats.mean());
StdOut.println("stddev = " + stats.stddev());
StdOut.println("95% confidence interval = " + stats.confidenceLo()
+ ", " + stats.confidenceHi());
}
/**
* Get sample mean of percolation threshold
*
* @return
*/
public double mean() {
return mean;
}
/**
* Get sample standard deviation of percolation threshold
*
* @return
*/
public double stddev() {
return stddev;
}
/**
* Get low endpoint of 95% confidence interval
*
* @return
*/
public double confidenceLo() {
return confidenceLo;
}
/**
* Get high endpoint of 95% confidence interval
*
* @return
*/
public double confidenceHi() {
return confidenceHi;
}
}
總結(jié):
如何根據(jù)API編譯JAVA程序:
1.把API所需要做的內(nèi)容全都用文字詳細寫出來,每個步驟該做什么;
2.可以寫出偽代碼,類似于算法課上所要求的,詳盡寫出;
3.選好數(shù)據(jù)結(jié)構(gòu),簡單變成不需要;
4.Coding
