Princeton Alhorithms Percolation

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 )、微信公衆號( 凝神長老和他的朋友們 )
凝神長老的二維碼們

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章