红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【CS61B】HW2-Percolation

HW2-Percolation

要求

实现蒙特卡洛模拟下的渗透模拟

具体来说,给一个N*N的方格,可以将某些方格挖空,若从最顶层挖,则上方会有水流流下,并蔓延到相邻的空格中。

不同开放方格概率下,生成渗透(水流从上贯通到下)的概率不同

\(N=100\)下,开放方格概率\(p*=0.593\)时,渗透率出现突变,意味着在这之前几乎没有可以渗透的情况,在这之后几乎都是渗透的情况

作业要求使用模拟求出不同情况下\(p*\)的价值

Percolation class

实现以下API

1
2
3
4
5
6
7
8
public class Percolation {
   public Percolation(int N)                // create N-by-N grid, with all sites initially blocked
   public void open(int row, int col)       // open the site (row, col) if it is not open already
   public boolean isOpen(int row, int col)  // is the site (row, col) open?
   public boolean isFull(int row, int col)  // is the site (row, col) full?
   public int numberOfOpenSites()           // number of open sites
   public boolean percolates()              // does the system percolate?
}

The WeightedQuickUnionUF class

作业提供了一个带权并查集的类,实现以上API时必须要用到该并查集,接口如下:

1
2
3
4
5
6
7
public class WeightedQuickUnionUF {
   public WeightedQuickUnionUF(int n)      // creates a UF with n elements
   public int count()                      // number of disjoint sets
   public int find(int p)                  // the root of p's set
   public boolean connected(int p, int q)  // whether p and q are in the same set
   public void union(int p, int q)         // join p and q's sets together
}

backwash问题

在试图使用一个并查集来维护连通性与是否填满时,出现了意料之外的bug

如图

此时B到N已经被打通,实现了渗透

这时打通LP,会发现程序也会将水填到这两块地方,因为在当前并查集中

  • L与P相连,P与(虚拟)底部相连
  • B与F与J与N相连,N与(虚拟)底部相连
  • 而两个(虚拟)底部是同一个,并查集会通过底部判断N与P相连

这就不符合我们的要求了

为解决这个问题,我们选择再开一个并查集单独维护某格是否被填满的情况,这个并查集不会与(虚拟)底部相连,也就避免了与连通性混淆,具体的:在row==size-1时,不对新并查集进行合并

1
2
3
4
5
6
if(row==0){
    uf.union(top,idx);
    ufFull.union(top, idx);

}
if(row==size-1) uf.union(bottom,idx);

以及判断时分开使用

1
2
3
4
5
6
public boolean isFull(int row, int col) {
    if (!valid(row, col)) throw new IndexOutOfBoundsException();
    if(!isOpen(row,col)) return false;
    int idx = convert(row, col);
    return ufFull.connected(top,idx);
}

虚拟顶部/底部问题

最初,本人使用0作为虚拟顶部,size-1作为虚拟底部,但这会造成下标的冲突

因为并查集中,对于每个坐标的idx是通过$ + $计算的,可能会造成size-1或0与计算的格点坐标冲突以导致结果错误。

因此,需要将虚拟顶部与底部的值设置一个超出格点范围(0~size*size-1)的值,这里我们设置虚拟顶部为\(\text{size}\times \text{size}\),虚拟底部为\(\text{size}\times \text{size}+1\)

1
2
top =size*size;
bottom = size*size+1;

实例变量设置

1
2
3
4
5
6
7
8
9
public int[][] grid; // 存储方格是被阻挡还是被挖空
public int open; // 存储挖空数量
public int size = 0; // 记录尺寸
private WeightedQuickUnionUF uf; // 记录是否渗透的并查集
private WeightedQuickUnionUF ufFull; // 记录某格是否填满的并查集
private int[] dx = {0,0,1,-1};
private int[] dy = {1,-1,0,0};
private int top;
private int bottom;

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Percolation(int N) {
    if(N<=0){
        throw new IllegalArgumentException();
    }
    grid = new int[N][N];
    open = 0;
    size = N;
    top =size*size;
    bottom = size*size+1;
    uf = new WeightedQuickUnionUF(size*size+2);
    ufFull = new WeightedQuickUnionUF(size*size+1);

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            grid[i][j] = 1;
        }
    }
}

对并查集初始化,并设置虚拟顶部与底部,将数组初始化为全被阻挡的初始状态

open

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void open(int row, int col) {
    if (!valid(row, col)) throw new IndexOutOfBoundsException();
    if (isOpen(row, col)) return;
    grid[row][col] = 0;
    open++;
    int idx = convert(row, col);
    if(row==0){
        uf.union(top,idx);
        ufFull.union(top, idx);

    }
    if(row==size-1) uf.union(bottom,idx);
    for (int i = 0; i<4;i++){

        int nr = row+dx[i];
        int nc = col+dy[i];
        if(nr < 0 ||nr>size-1|| nc < 0 ||nc>size-1) continue;
        if (isOpen(nr,nc)){
            int tidx = convert(nr,nc);
            uf.union(idx, tidx);
            ufFull.union(idx, tidx);
        }
    }

}

首先判断是否合法以及是否已经是开放

然后将该格标记为开放,并计算该格对应并查集idx

为了防止backwash,第一行都与顶部连通,而最后一行判断full的并查集不与底部连通

循环判断四个方位是否连通,若连通则在并查集标记

isFull

1
2
3
4
5
6
public boolean isFull(int row, int col) {
    if (!valid(row, col)) throw new IndexOutOfBoundsException();
    if(!isOpen(row,col)) return false;
    int idx = convert(row, col);
    return ufFull.connected(top,idx);
}

判满使用单独的并查集,计算idx值并判断与顶部是否连通即可

percolates

1
2
3
public boolean percolates() {
    return uf.connected(top,bottom);
}

判断是否渗透,用渗透并查集判断顶部与底部是否相连即可