Coursera 普林斯顿Algorithms part i第一周作业Union-Find

题目复述

在一个n*n的网格上,每一块都是一个管道,一开始全部处于关闭状态。我们所要做的就是不断随机打开一个管道,直到系统可以渗透,即存在一条路径使得从最上面的某个管道注水后可以从最下面的某个管道中流出,记录下此时打开的管道数量占总管道数量的比值p*。(通过大量重复实验得知,对于固定的n值,系统可以渗透时的p*值是稳定的,并且在p < p*时,系统几乎不会渗透,在p > p*时,系统几乎可以稳定渗透)。为了达到这个目的,题目给了两个API Percolation类和PercolationStat类。如下

Percolation.java

public class Percolation {

    // creates n-by-n grid, with all sites initially blocked
    public Percolation(int n)

    // opens the site (row, col) if it is not open already
    public void open(int row, int col)

    // is the site (row, col) open?
    public boolean isOpen(int row, int col)

    // is the site (row, col) full?
    public boolean isFull(int row, int col)

    // returns the number of open sites
    public int numberOfOpenSites()

    // does the system percolate?
    public boolean percolates()

    // test client (optional)
    public static void main(String[] args)
}

PercolationStat

public class PercolationStats {

    // perform independent trials on an n-by-n grid
    public PercolationStats(int n, int trials)

    // sample mean of percolation threshold
    public double mean()

    // sample standard deviation of percolation threshold
    public double stddev()

    // low endpoint of 95% confidence interval
    public double confidenceLo()

    // high endpoint of 95% confidence interval
    public double confidenceHi()

   // test client (see below)
   public static void main(String[] args)

}

算法实现

Percolation.java

import edu.princeton.cs.algs4.WeightedQuickUnionUF;
public class Percolation {
	private int n;				//网格大小n * n
    private WeightedQuickUnionUF uf, test; //uf包含上、下两个虚节点 test只包含上虚节点
	private boolean [][]open;	// 各个节点的状态信息 打开:true 关闭:false
	private int count = 0;		// 已打开的节点数
	private static final  int VTOP = 0;
	private int VBOTTOM;
	public Percolation(int n)
	{
			if (n > 0)
			{
				this.n = n;
				VBOTTOM = n * n + 1;
				uf = new WeightedQuickUnionUF(n * n + 2); //并查集初始需要n * n + 2个节点,0代表上虚节点,1-n^2代表实结点,n^2+1代表下虚节点
				test = new WeightedQuickUnionUF(n * n + 2);
				open = new boolean[n + 1][n + 1];
			}
			else {
				throw new IllegalArgumentException();
			}
		} 
	
	public void open(int row, int col)
	{
			if (row <= n && col <= n && row >= 1 && col >= 1)
			{
				if (!open[row][col])
				{
				open[row][col] = true;
				this.count++;
				int idx = tTo(row, col);
				if(row == 1)			// 如果是第一行,则和上虚节点合并	
				{
					uf.union(VTOP, idx);
					test.union(VTOP, idx);
				}
				if(row == n)
				{
					uf.union(VBOTTOM, idx); //如果是最后一行,则只有uf和下虚节点合并
				}
				if (row > 1 && open[row - 1][col]) {
					uf.union(idx, idx - n); // 与上面合并
					test.union(idx, idx - n);
				}
					 
				if (row < n && open[row + 1][col]) {
					uf.union(idx, idx + n); // 与下面合并
					test.union(idx, idx + n);
				}
					
				if (col > 1 && open[row][col - 1])
				{
					uf.union(idx, idx - 1); // 与左侧合并
					test.union(idx, idx - 1);
				}
					
				if (col < n && open[row][col + 1]) 
				{
					uf.union(idx, idx + 1); // 与右侧合并
					test.union(idx, idx + 1);
				}
				
				}
			}	
			else {
				throw new IllegalArgumentException();
			}
			
	}
	
	public boolean isOpen(int row, int col)
	{
		boolean flag = false;
			if (row <= n && col <= n && row >= 1 && col >= 1)
			{
				if (open[row][col])
					flag = true;
				return flag;
			}
		
			else {
				throw new IllegalArgumentException();
			}
	}
	
	public boolean isFull(int row, int col)
	{
			if (row <= n && col <= n && row >= 1 && col >= 1)
			{
				boolean flag = false;
				int idx = tTo(row, col);
				if (open[row][col] && test.find(idx) == test.find(VTOP))
					flag = true;
				return flag;
			}
			else {
				throw new IllegalArgumentException();
			}
	}
	
	public int numberOfOpenSites()
	{
		return this.count;
	}
	
	public boolean percolates()
	{
		boolean flag = false;
		if (uf.find(VTOP) == uf.find(VBOTTOM))
			flag = true;
		return flag;
	}
	private int tTo(int row, int col)
	{
		return (row - 1) * n + col;
	}
}

open(int row, int col):首先检验该管道是否在第一行,若是则将其与上虚节点合并,并打开;其次检验其是否在最后一行(ps.这里由于存在n = 1的可能,所以不能用else if),若是,则只将uf中对应的管道与下虚节点合并(具体原因在下面介绍);然后,将其与上下左右四个管道进行合并(如果存在并且已打开的话)。

isFull(int row, int col): 检验其是否打开并且是否与上虚节点在同一堆中(这里需要注意,只能在test中进行检验)

percolates():检验系统是否渗透,即上下虚节点是否在同一堆中。原理如下图

这是一个相当聪明的做法,正常情况下,检验系统是否可渗透需要对每个底部管道进行遍历,检验其是否与某个顶部节点在同一个堆中,而这需要的时间复杂度是O(n^2)。而添加上下虚节点后时间复杂度就变成了常数,只需检验上下虚节点是否在同一堆中就可判断系统是否可渗透。

每当顶部管道打开就将其与上虚节点相连(在两个并查集中都需要这么做) ,每当最底部管道打开就将其与下虚节点相连(仅在uf并查集中进行这个操作,这个操作会产生一个副作用,那就是一旦打开一个底部管道后,其将会和其他已打开的底部管道通过下虚节点相连从而在一个堆内,那么一旦有一个底部管道可以渗透,即与顶部的一个管道相连,那么其他底部管道也会随之处于可以渗透的状态,但情况往往不是这样)以下图为例。

 如上图所示,位于(10,10)位置的管道打开后整个系统可以渗透,这也会随之导致底部其他位置如(10,1)(10,2)(10,8)的管道处于Full状态,而事实是他们并不可渗透。

PercolationStat.java

import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdRandom;
import edu.princeton.cs.algs4.StdStats;
public class PercolationStats {
    private final static double CONFIDENCE_95 = 1.96;
    private int  trials, size;
    private double []mean;
    private double meanValue, meanStddev;
   
	// PERFORM INDEPENDENtrials trialsRIALS ON AN N-BY-N GRID
	public PercolationStats(int n, int trials)
	{
		if (n > 0 && trials > 0)
		{
			this.size = n * n;
			this.trials = trials;
			this.mean = new double[trials];
			for (int i = 0; i < trials; i++)					// trials次实验
			{
				Percolation pc= new Percolation(n);
				for (int j = 1; j <= size; j++)	// 随机选择要开放的块
				{
					int random = StdRandom.uniform(1, size + 1);
					int row = (random - 1) / n + 1, col = (random - 1) % n + 1;
					while(pc.isOpen(row, col))
					{
						random = StdRandom.uniform(1, size + 1);
						row = (random - 1) / n + 1;
						col = (random - 1) % n + 1;
					}
					pc.open(row, col);		// open(row, col)
					if (pc.percolates())
					{
						this.mean[i] = (double) pc.numberOfOpenSites() / size;
						break;
					}
				}
			}
			this.meanValue = StdStats.mean(mean);
			if(trials == 1) this.meanStddev = Double.NaN;
			else this.meanStddev = StdStats.stddev(mean);
		}
		else {
			throw new IllegalArgumentException();
		}

	}
	
	// sample mean of percolation threshold
	public double mean()
	{
		return this.meanValue;
	}
	
	// sample stndard deviation of percolation threshold
	public double stddev()
	{
		return this.meanStddev;
	}
	
	// low endpoint of 95% confidence interval
	public double confidenceLo()
	{
		return this.meanValue - CONFIDENCE_95 * this.meanStddev / Math.sqrt(trials);
	}
	
	// high endpoint of 95% confidence interval
	public double confidenceHi()
	{
		return this.meanValue + CONFIDENCE_95 * this.meanStddev / Math.sqrt(trials);
	}
	
	
	public static void main(String[] args)
	{
		
		int n = Integer.parseInt(args[0]), trials = Integer.parseInt(args[1]);
		PercolationStats ps = new PercolationStats(n, trials);
		double mean = ps.mean(), stddev = ps.stddev(), low = ps.confidenceLo(), high = ps.confidenceHi();
		StdOut.printf("mean                    = %f\n", mean);
		StdOut.printf("stddev                  = %f\n", stddev);
		StdOut.printf("95%% confidence interval = [%f, %f]\n", low, high);
		
	}
}

这里需要注意一下几点:

1、在用到算法库中的StdRandom.uniform函数时,每次在1-n^2中随机选择一个数代表需要打开的管道。这里有一个坑,就是随机函数可能会随机到同一个数,这在n较小的情况下出现的可能性非常大,所以,每次随机打开管道时都需要进行检验,如果该管道已经打开,就重新随机一个管道打开,直到随机到的管道未打开。

2、将T次实验求得的p值存在一个数组中,然后利用自带的库函数求均值、标准差、置信区间等即可。

3、在main函数中,输入并不是从标准输入中读取,而是从命令行中读取,所以需要将args[]数组中的String转化为数字作为输入,赋值给n和trials

4、由于要进行T次实验,所以需要构造trials次Percolation类,类的声明要放在trials的循环里面,以便在每次循环结束后创建的Percolation会进行销毁回收,从而不会导致内存占用过大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值