Princeton Alhorithms Percolation
普林斯頓大學算法課第 1 次作業“滲透模型 ”。
這是一道並查集的問題。
按照題目要求逐步定義 isFull 和 isOpen 是沒有難度的,然後只需要在 open 操作中,設置一個 openStatus 爲 true 並將它與它周圍的四個格子連通即可。
注意 isFull 必須首先滿足 isOpen。
這題想要通過,80 分,非常簡單,難點在於得高分,因爲需要優化的地方非常多,也細節。
首先一個技巧,是 ACM 競賽中常用的,虛擬起點與虛擬終點,這樣可以加速是否連通的判斷,不需要遍歷整個最後一行,只需要直接看虛擬點是否連接。
由此,我們可以輕鬆得到 95 分。
但是這樣還有 3 個 backwash,分別是 Test Case 16 – 18。
比如一個 3×3 的網格,我們先打開 (1, 1), (1, 2), (1, 3), (2, 3), (3, 3) 和 (3, 1),由於我們打開到 (3, 3) 的時候,事實上它已經滲透,所以虛擬起點和虛擬終點已經連接,此時我們打開 (3, 1) 之後,它會通過虛擬終點連接到 (3, 3),並逐步向上直到第一層,最終與虛擬起點連通。這會導致在判斷 (3, 1) 的 isFull() 出錯。
爲此,我們維護另一個並查集,該並查集只有虛擬起點,沒有虛擬終點。
以下代碼獲得 100 分。
import edu.princeton.cs.algs4.WeightedQuickUnionUF;
public class Percolation {
private final int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
// 虛擬起點,減少 UnionFind 的調用
private static final int TOP = 0;
private boolean[][] openStatus;
private final int n;
private final WeightedQuickUnionUF disjointSet;
// 爲了解決 backwash,這裏只用於 TOP 連接
private final WeightedQuickUnionUF disjointSetBackwash;
// 虛擬終點,減少 UnionFind 的調用
private final int bottom;
// 直接計入 counter,避免 isOpen() 去遍歷
private int counter;
public Percolation(int n) {
if (n <= 0) {
throw new IllegalArgumentException();
}
this.n = n;
openStatus = new boolean[n + 1][n + 1];
bottom = n * n + 1;
disjointSet = new WeightedQuickUnionUF(bottom + 1);
disjointSetBackwash = new WeightedQuickUnionUF(bottom + 1);
}
public void open(int row, int col) {
if (isValid(row, col)) {
if (!openStatus[row][col]) {
// 沒有打開過才能加 1
counter++;
openStatus[row][col] = true;
// 將第一層與虛擬起點連接,最後一層與虛擬終點連接
int unionIndex = (row - 1) * n + col;
if (row == 1) {
disjointSet.union(TOP, unionIndex);
disjointSetBackwash.union(TOP, unionIndex);
}
// 這裏不是 else if 因爲 n = 1 的時候,頭尾都要連通
if (row == n) {
disjointSet.union(bottom, unionIndex);
// 不在 backwash 中記錄
}
}
for (int[] ints : directions) {
int newRow = row + ints[0];
int newCol = col + ints[1];
if (isValid(newRow, newCol) && isOpen(newRow, newCol)) {
int unionIndexP = (row - 1) * n + col;
int unionIndexQ = (newRow - 1) * n + newCol;
disjointSet.union(unionIndexP, unionIndexQ);
disjointSetBackwash.union(unionIndexP, unionIndexQ);
}
}
} else {
throw new IllegalArgumentException();
}
}
private boolean connectedWithTop(int row, int col) {
int unionIndex = (row - 1) * n + col;
return disjointSetBackwash.connected(TOP, unionIndex);
}
private boolean isValid(int row, int col) {
return row >= 1 && row <= n && col >= 1 && col <= n;
}
public boolean isOpen(int row, int col) {
if (isValid(row, col)) {
return openStatus[row][col];
} else {
throw new IllegalArgumentException();
}
}
public boolean isFull(int row, int col) {
if (isValid(row, col)) {
// A full site is an open site that can ....
// 所以 isFull 必須要先滿足 isOpen
return isOpen(row, col) && connectedWithTop(row, col);
} else {
throw new IllegalArgumentException();
}
}
public int numberOfOpenSites() {
return counter;
}
public boolean percolates() {
return disjointSet.connected(TOP, bottom);
}
public static void main(String[] args) {
Percolation p;
p= new Percolation(2);
p.open(2, 1);
p.open(2, 2);
p.open(1, 2);
assert p.percolates();
// Backwash
// (3, 1) 通過虛擬終點與起點連接,但它其實不是 full 的
p = new Percolation(3);
p.open(1, 1);
p.open(1, 2);
p.open(1, 3);
p.open(2, 3);
p.open(2, 2);
p.open(3, 3);
p.open(3, 1);
assert !p.isFull(3, 1);
assert p.percolates();
}
}
import edu.princeton.cs.algs4.StdRandom;
import edu.princeton.cs.algs4.StdStats;
public class PercolationStats {
private static final double CONFIDENCE = 1.96;
private final double[] thresholds;
// 緩存,以減少 Stats 的調用,否則會扣分
private final double mean;
private final double stddev;
public PercolationStats(int n, int trials) {
if (!isValid(n, trials)) {
throw new IllegalArgumentException();
}
thresholds = new double[trials];
for (int i = 0; i < trials; ++i) {
Percolation p = new Percolation(n);
while (!p.percolates()) {
int possibleRow = StdRandom.uniform(1, n + 1);
int possibleCol = StdRandom.uniform(1, n + 1);
p.open(possibleRow, possibleCol);
}
thresholds[i] = (double) (p.numberOfOpenSites()) / (double) (n * n);
}
mean = StdStats.mean(thresholds);
stddev = StdStats.stddev(thresholds);
}
private boolean isValid(int n, int trials) {
return n > 0 && trials > 0;
}
public double mean() {
return mean;
}
public double stddev() {
return stddev;
}
public double confidenceLo() {
return mean - CONFIDENCE * stddev / Math.sqrt(thresholds.length);
}
public double confidenceHi() {
return mean + CONFIDENCE * stddev / Math.sqrt(thresholds.length);
}
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
int trials = Integer.parseInt(args[1]);
PercolationStats ps = new PercolationStats(n, trials);
System.out.println("mean=" + ps.mean());
System.out.println("stddev=" + ps.stddev());
System.out.println("95% confidence interval=[" + ps.confidenceLo() + "," + ps.confidenceHi() + "]");
}
}
歡迎關注我的個人博客以閱讀更多優秀文章:凝神長老和他的朋友們(https://www.jxtxzzw.com)
也歡迎關注我的其他平臺:知乎( https://s.zzw.ink/zhihu )、知乎專欄( https://s.zzw.ink/zhuanlan )、嗶哩嗶哩( https://s.zzw.ink/blbl )、微信公衆號( 凝神長老和他的朋友們 )