Programming Assignment 1: Percolation
Write a program to estimate the value of the percolation threshold via Monte Carlo simulation.
Algorithm 第一周的编程任务,主要目的是编写两个类,来进行Monte Carlo模拟,大致估计出渗滤阈值(percolation threshold)。
课程给出的两个数据类型说明(API):
Percolation : To model a percolation system, create a data type Percolation
with the following API:
public class Percolation { public Percolation(int n) // create n-by-n grid, with all sites blocked public void open(int row, int col) // open site (row, col) if it is not open already public boolean isOpen(int row, int col) // is site (row, col) open? public boolean isFull(int row, int col) // is site (row, col) full? public int numberOfOpenSites() // number of open sites public boolean percolates() // does the system percolate? public static void main(String[] args) // test client (optional) }
PercolationStats: To perform a series of computational experiments with the following API:
public class PercolationStats { public PercolationStats(int n, int trials) // perform trials independent experiments on an n-by-n grid public double mean() // sample mean of percolation threshold public double stddev() // sample standard deviation of percolation threshold public double confidenceLo() // low endpoint of 95% confidence interval public double confidenceHi() // high endpoint of 95% confidence interval public static void main(String[] args) // test client (described below) }
可能需要导入的包
import edu.princeton.cs.algs4.StdRandom;
import edu.princeton.cs.algs4.StdStats;
import edu.princeton.cs.algs4.WeightedQuickUnionUF;
方案
课程指出可以建立两个虚拟节点,通过与这两个节点是否相连,来指示节点是顶部节点还是底部节点,而判断是否渗滤的方法只需要判断顶部节点和底部节点是否相连即可。
存在问题
但是考虑到要实现isFull(int row, int col)
方法,即判断当前节点是否顶部节点相连,会存在backwash问题。如下图(B点和C点均为虚拟节点):
A点和C点实际上并不相连,但是因为A点和B点相连,而B点和C点相连,所以系统认为A点和C点相连。
解决方法
我们可以再创建一个不包含B节点的WeightedQuickUnionUF
对象,避免backwash的问题,如下图:
我们可以看到此时系统不会在认为A点和C点相连。
已知弊端
因为需要存储两个WeightedQuickUnionUF
对象所以不可避免造成了内存的浪费。
实现代码
import edu.princeton.cs.algs4.WeightedQuickUnionUF;
public class Percolation {
private boolean[] opened;
private boolean percolated;
private int numOfOpenSites = 0;
private final WeightedQuickUnionUF hasBackwash;
private final WeightedQuickUnionUF antiBackwash;
private final int len;
private final int virtualTop = 0;
private final int virtualBottom;
/**
* Initializes an grid of all sites are blocked.
*
* @param n is the length of square;
* <p>
* throw IllegalArgumentException when the param is less than 1.
* @throws IllegalArgumentException when the param is less than 1;
*/
public Percolation(int n) {
if (n < 1) {
throw new IllegalArgumentException();
}
len = n;
int numOfSites = len * len + 2;
virtualBottom = numOfSites - 1;
opened = new boolean[numOfSites - 2];
hasBackwash = new WeightedQuickUnionUF(numOfSites);
antiBackwash = new WeightedQuickUnionUF(numOfSites - 1);
}
/**
* open site if it is not open already
*
* @throws IllegalArgumentException when {@code row} or {@code col} is illegal.
*/
public void open(int row, int col) {
legal(row, col);
// open site (row, col) if it is not open already
if (isOpen(row, col)) {
return;
}
numOfOpenSites++;
int currentIndex = xyTo1D(row, col);
opened[currentIndex] = true;
if (row == 1) {
hasBackwash.union(virtualTop, currentIndex);
antiBackwash.union(virtualTop, currentIndex);
}
if (row == len) {
hasBackwash.union(virtualBottom, currentIndex);
}
int[] dx = {1, -1, 0, 0};
int[] dy = {0, 0, 1, -1};
for (int i = 0; i < 4; i++) {
int adjacentX = row + dx[i];
int adjacentY = col + dy[i];
if (valid(adjacentX, adjacentY) && isOpen(adjacentX, adjacentY)) {
int adjacentIndex = xyTo1D(adjacentX, adjacentY);
hasBackwash.union(adjacentIndex, currentIndex);
antiBackwash.union(adjacentIndex, currentIndex);
}
}
}
/**
* Check whether site is opened.
*
* @return true when the site is opened.
* @throws IllegalArgumentException if row or col is illegal.
*/
public boolean isOpen(int row, int col) {
// is site (row, col) open?
legal(row, col);
return opened[xyTo1D(row, col)];
}
/**
* Check whether the site is full.
*
* @return true when the site is full.
*/
public boolean isFull(int row, int col) {
// is site (row, col) full?
legal(row, col);
return isOpen(row, col) && antiBackwash.connected(virtualTop, xyTo1D(row, col));
}
/**
* return the number of opened sites.
*
* @return the number of opened sites.
*/
public int numberOfOpenSites() {
return numOfOpenSites;
}
/**
* Check whether the system percolation.
*
* @return true when system percolation.
*/
public boolean percolates() {
// does the system percolate?
if (percolated) {
return true;
}
if (hasBackwash.connected(virtualBottom, virtualTop)) {
percolated = true;
}
return percolated;
}
/**
* check whether the {@code row} and {@code col} is legal.
*
* @throws IllegalArgumentException when {@code row} or {@code col} is illegal.
*/
private void legal(int row, int col) {
if (row < 1 || row > len || col < 1 || col > len) {
throw new IllegalArgumentException();
}
}
/**
* check whether the row and col is valid.
*
* @return false when row or col is invalid.
*/
private boolean valid(int row, int col) {
return row >= 1 && row <= len && col >= 1 && col <= len;
}
/**
* Convert 2D index into 1D index.
*
* @return 2D index.
*/
private int xyTo1D(int row, int col) {
return (row - 1) * len + col - 1;
}
}
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdRandom;
import edu.princeton.cs.algs4.StdStats;
public class PercolationStats {
private final double[] x;
private final double xMean;
private final double s;
private static final double CONFIDENCE_95 = 1.96;
/**
* Perform trials independent experiments on an n-by-n grid
*/
public PercolationStats(int n, int trials) {
if (n <= 0 || trials <= 0) {
throw new IllegalArgumentException("illegal parameter");
}
x = new double[trials];
experiment(n, trials);
xMean = StdStats.mean(x);
s = StdStats.stddev(x);
}
/**
* Experiment.
*/
private void experiment(int n, int trials) {
Percolation percolation;
boolean[] isEmptyLine;
int numOfLine;
for (int i = 0; i < trials; i++) {
isEmptyLine = new boolean[n];
numOfLine = 0;
percolation = new Percolation(n);
while (true) {
int posX = StdRandom.uniform(n) + 1;
int posY = StdRandom.uniform(n) + 1;
if (!percolation.isOpen(posX, posY)) {
percolation.open(posX, posY);
x[i]++;
if (!isEmptyLine[posX - 1]) {
isEmptyLine[posX - 1] = true;
numOfLine++;
}
if (numOfLine == n) {
if (percolation.percolates()) {
break;
}
}
}
}
x[i] /= n * n;
}
}
/**
* Unit tests the {@code Percolation} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
// test client (described below)
if (args.length != 2) {
System.out.println("Usage: PercolationStats N T");
return;
}
int n = Integer.parseInt(args[0]);
int trials = Integer.parseInt(args[1]);
PercolationStats percStats = new PercolationStats(n, trials);
System.out.printf("mean = %f\n", percStats.mean());
System.out.printf("stddev = %f\n", percStats.stddev());
StdOut.printf("95%% confidence interval = [%f, %f]\n", percStats.confidenceLo(), percStats.confidenceHi());
}
/**
* Return sample mean of percolation threshold.
*
* @return sample mean of percolation threshold;
*/
public double mean() {
return xMean;
}
/**
* Return sample standard deviation of percolation threshold.
*
* @return sample standard deviation of percolation threshold;
*/
public double stddev() {
if (x.length == 1) {
return Double.NaN;
}
return s;
}
/**
* Return low endpoint of 95% confidence interval.
*
* @return low endpoint of 95% confidence interval;
*/
public double confidenceLo() {
return xMean - (CONFIDENCE_95 * s / Math.sqrt(x.length));
}
/**
* Return high endpoint of 95% confidence interval.
*
* @return high endpoint of 95% confidence interval;
*/
public double confidenceHi() {
return xMean + (CONFIDENCE_95 * s / Math.sqrt(x.length));
}
}
其他方案
为了减少内存的使用我们仅考虑使用一个WeightedQuickUnionUF
对象,且不再使用虚拟节点。此时此时我们需要记录每个节点的状态,包括以下几种情况:
- BLOCKED标识为
0
(0000 0000
) - OPEN标识为
1
(0000 0001
) - TOP_CONNECTED标识为
2
(0000 0010
) - BOTTOM_CONNECTED标识为
4
(0000 0100
) - PERCOLATED标识为
7
(0000 0111
)
使用byte数组来存储每一个节点的属性,在进行union操作时,将两个节点对应的状态进行|运算,即可获得两个节点的合并属性,使用&运算符可用来判断节点是否具有某个属性。
实现代码
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.WeightedQuickUnionUF;
/**
* @author revc
*/
public class Percolation {
private static final byte OPEN = 1;
private static final byte TOP_CONNECTED = 2;
private static final byte BOTTOM_CONNECTED = 4;
private static final byte PERCOLATED = 7;
private final int len;
private byte[] state;
private int numOfOpenSites;
private boolean percolated = false;
private final WeightedQuickUnionUF grid;
/**
* Initializes an grid of all sites are blocked.
*
* @param n is the length of square;
*
* Throw IllegalArgumentException when the param is less than 1.
*
* @throws IllegalArgumentException when the param is less than 1;
*/
public Percolation(int n) {
if (n < 1) {
throw new IllegalArgumentException();
}
len = n;
state = new byte[n * n];
grid = new WeightedQuickUnionUF(n * n);
}
/**
* Unit tests the {@code Percolation} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
int n = StdIn.readInt();
Percolation client = new Percolation(n);
while (!StdIn.isEmpty()) {
int row = StdIn.readInt();
int col = StdIn.readInt();
client.open(row, col);
StdOut.println(client.isOpen(row, col));
StdOut.println(client.isFull(row, col));
StdOut.println(client.percolates());
}
}
/**
* open site if it is not open already
*
* @throws IllegalArgumentException when {@code row} or {@code col} is illegal.
*/
public void open(int row, int col) {
legal(row, col);
int currentIndex = xyTo1D(row, col);
//if the site has been opened then return
if (0 != (state[currentIndex] & OPEN)) {
return;
}
state[currentIndex] = (byte) (state[currentIndex] | OPEN);
if (row == 1) {
state[currentIndex] = (byte) (state[currentIndex] | TOP_CONNECTED);
}
if (row == len) {
state[currentIndex] = (byte) (state[currentIndex] | BOTTOM_CONNECTED);
}
int[] xDiff = {0, 0, -1, 1};
int[] yDiff = {-1, 1, 0, 0};
for (int i = 0; i < xDiff.length; i++) {
int adjacentX = row + xDiff[i];
int adjacentY = col + yDiff[i];
if (valid(adjacentX, adjacentY)) {
int adjSiteIndex = xyTo1D(adjacentX, adjacentY);
int rootIndex = grid.find(adjSiteIndex);
if (OPEN == (state[adjSiteIndex] & OPEN)) {
//The currentSite saves the attributes of each root sites and finally appends all attributes to the
//real root site.
state[currentIndex] = (byte) (state[currentIndex] | state[rootIndex]);
grid.union(currentIndex, rootIndex);
}
}
}
int finalRoot = grid.find(currentIndex);
state[finalRoot] = (byte) (state[currentIndex] | state[finalRoot]);
if (PERCOLATED == (byte) (state[finalRoot] & PERCOLATED)) {
percolated = true;
}
numOfOpenSites++;
}
/**
* Check whether site is opened.
*
* @return true when the site is opened.
* @throws IllegalArgumentException if row or col is illegal.
*/
public boolean isOpen(int row, int col) {
legal(row, col);
return OPEN == (state[xyTo1D(row, col)] & OPEN);
}
/**
* Check whether the site is full.
*
* @return true when the site is full.
*/
public boolean isFull(int row, int col) {
legal(row, col);
int rootIndex = grid.find(xyTo1D(row, col));
return isOpen(row, col) && TOP_CONNECTED == (TOP_CONNECTED & state[rootIndex]);
}
/**
* return the number of opened sites.
*
* @return the number of opened sites.
*/
public int numberOfOpenSites() {
return numOfOpenSites;
}
/**
* Check whether the system percolation.
*
* @return true when system percolation.
*/
public boolean percolates() {
return percolated;
}
/**
* check whether the {@code row} and {@code col} is legal.
*
* @throws IllegalArgumentException when {@code row} or {@code col} is illegal.
*/
private void legal(int row, int col) {
if (row < 1 || row > len || col < 1 || col > len) {
throw new IllegalArgumentException();
}
}
/**
* check whether the row and col is valid.
*
* @return false when row or col is invalid.
*/
private boolean valid(int row, int col) {
return row >= 1 && row <= len && col >= 1 && col <= len;
}
/**
* Convert 2D index into 1D index.
*
* @return 2D index.
*/
private int xyTo1D(int row, int col) {
return (row - 1) * len + col - 1;
}
}