使用合并-查找(union-find)数据结构,编写程序通过蒙特卡罗模拟(Monte Carlo simulation)来估计渗透阈值。
- List item
- 我们使用 N×N 网格点来模型化一个渗透系统。 每个格点或是 open 格点或是 blocked格点。 一个 full site 是一个 open 格点,它可以通过一系列的邻近(左、右、上、下 )open 格点连通到顶行的一个 open 格点。如果在底行中有一个 full site 格点,则称系统是渗透的。(对于绝缘/金属材料的例子,open 格点对应于金属材料,渗透系统有一条从顶行到底行的金属路径,且 full sites 格点导电。对于多孔物质示例,open 格点对应于空格,水可能流过,从而渗
透系统使水充满 open 格点,自顶向下流动。)
问题:
在一个著名的科学问题中,研究人员对以下问题感兴趣:如果将格点以概率 p 独立地设置为 open 格点(因此以概率 1-p 被设置为 blocked 格点),系统渗透的概率是多少? 当 p = 0 时,系统不会渗出; 当 p=1 时,系统渗透。下图显示了 20×20 随机网格(左)和 100×100 随机网格(右)的格点空置概率 p 与渗滤概率。
我们使用 N×N 网格点来模型化一个渗透系统。 每个格点或是 open 格点或是 blocked格点。 一个 full site 是一个 open 格点,它可以通过一系列的邻近(左、右、上、下)open 格点连通到顶行的一个 open 格点。如果在底行中有一个 full site 格点,则称系统是渗透的。(对于绝缘/金属材料的例子,open 格点对应于金属材料,渗透系统有一条从顶行到底行的金属路径,且 full sites 格点导电。对于多孔物质示例,open 格点对应于空格,水可能流过,从而渗
透系统使水充满 open 格点,自顶向下流动的问题。 在一个著名的科学问题中,研究人员对以下问题感兴趣:如果将格点以概率 p 独立地设置为 open 格点(因此以概率 1-p 被设置为 blocked 格点),系统渗透的概率是多少? 当 p
= 0 时,系统不会渗出; 当 p=1 时,系统渗透。下图显示了 20×20 随机网格(左)和 100×100 随机网格(右)的格点空置概率 p 与渗滤概率。
- 3 -
当 N 足够大时,存在阈值 p*,使得当 p <p*,随机 N× N 网格几乎不会渗透,并且当 p> p*
时,随机 N× N 网格几乎总是渗透。 尚未得出用于确定渗滤阈值 p的数学解。你的任务是
编写一个计算机程序来估计 p。
Percolation 数据类型。模型化一个 Percolation 系统,创建含有以下 API 的数据类型 Percolation。
public class Percolation {
public Percolation(int N) // create N-by-N grid, with all sites blocked
public void open(int i, int j) // open site (row i, column j) if it is not already
public boolean isOpen(int i, int j) // is site (row i, column j) open?
public boolean isFull(int i, int j) // is site (row i, column j) full?
public boolean percolates() // does the system percolate?
public static void main(String[] args) // test client, optional
}
约定行 i 列 j 下标在 1 和 N 之间,其中(1, 1)为左上格点位置:如果 open(), isOpen(), or isFull()不在这个规定的范围,则抛出IndexOutOfBoundsException 例外。如果 N ≤ 0,构造函数应该抛出 IllegalArgumentException 例外。构造函数应该与 N2成正比。所有方法应该为常量时间加上常量次调用合并-查找方法 union(), find(), connected(), and count()。
通过重复该计算实验 T 次并对结果求平均值,我们获得了更准确的渗滤阈值估计。 令 xt
是第 t 次计算实验中 open 格点所占比例。样本均值μ提供渗滤阈值的一个估计值;样本标准
差σ测量阈值的灵敏性。
我们创建数据类型 PercolationStats 来执行一系列计算实验,包含以下 API。
public class PercolationStats {
public PercolationStats(int N, int T) // perform T independent computational 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() // returns lower bound of the 95% confidence interval
public double confidenceHi() // returns upper bound of the 95% confidence interval
public static void main(String[] args) // test client, described below
}
在 N ≤ 0 或 T ≤ 0 时,构造函数应该抛出 java.lang.IllegalArgumentException 例外。
此外,还包括一个 main( )方法,它取两个命令行参数 N 和 T,在 N×N 网格上进行 T 次独
立的计算实验(上面讨论),并打印出均值μ、标准差σ和 95% 渗透阈值的置信区间。 使
用标准库中的标准随机数生成随机数; 使用标准统计库来计算样本均值和标准差。
Example values after creating PercolationStats(200, 100)
mean() = 0.5929934999999997
stddev() = 0.00876990421552567
confidenceLow() = 0.5912745987737567
confidenceHigh() = 0.5947124012262428
Example values after creating PercolationStats(200, 100)
mean() = 0.592877
stddev() = 0.009990523717073799
confidenceLow() = 0.5909188573514536
confidenceHigh() = 0.5948351426485464
Example values after creating PercolationStats(2, 100000)
mean() = 0.6669475
stddev() = 0.11775205263262094
confidenceLow() = 0.666217665216461
confidenceHigh() = 0.6676773347835391
运行时间和内存占用分析。 - 5 -
使用 quick-find 算法(QuickFindUF.java from algs4.jar)实现 Percolation 数据类型。进行实验
表明当 N 加倍时对运行时间的影响;使用近似表示法,给出在计算机上的总时间,它是输
入 N 和 T 的函数表达式。
使用 weighted quick-union 算法(WeightedQuickUnionUF.java from algs4.jar)实现 Percolation
数据类型。进行实验表明当 N 加倍时对运行时间的影响;使用近似表示法,给出在计算机
上的总时间,它是输入 N 和 T 的函数表达式。
解析:
通过所学的连通性判断方法和在NN的网格上随机的打开渗透点直到顶和底联通,分别使用quick_find,union_find和union_find加权来进行连通性判断,并分别分析所用时间和结果的正确性。
本质其实就是对于一个NN的全闭合的格子使用随机数的函数分别随机出行数和列数,每次开一个格点,用quickfind和unionfind和unionfind加权的方法把这个格点连接到已有的联通分量里。直到最顶行和最低行连通。
实现结果如下:
当n为40时:
当N为80时:
当n为160时:
结果分析
Quickfind在n越来越大后他的执行效率时最慢的时间复杂度分析:
Quickfind每次开一个点都要将整个数组遍历一遍,把和该点联通的所有点的值改为这个点的下标所以算法的复杂度为n平方的。这里的n为二维矩阵的边长,所以时间的的增量为每次矩阵增量的4次方,这里我的例子里每次的增量为2,所以理论每次的时间应翻16倍,但这个算法每次所开的点比例为0.59,所以时间翻倍为0.5916约为9.44大概为10,通过上面的时间比,大概也趋向于10。
Union_find和Union_find加权的算法的时间复杂度都为nlogn,经过平方后2n平方*logn增量为2时比例变化,无固定比值,但都满足所验证的结论。
package 渗透;
public class Percolation //第一个类定义三种判断连通性的方法
{
public int N;
public int count_open=0;
public int [][]box;
public int []id;
public int top,bottom;
private int []sz;
private boolean connected=true;
private boolean unconnected=false;
public Percolation(int N)
{
box=new int [N][N];
id=new int [N*N+2];
sz=new int [N*N+2];
top=N*N;
bottom=N*N+1;
id[top]=top;
id[bottom]=bottom;
int k=0;
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++,k++)//初始N*N的矩,让其都为0,同时其对应的id【】其顺位的的值,sz【】全初始为1
{
box[i][j]=0;
id[k]=k;
sz[k]=1;
}
}
for(int i=0;i<N;i++)
{
id[i]=id[top];//顶行的值都为n平方
}
for(int i=N*(N-1);i<N*N;i++)
{
id[i]=id[bottom];//底行的值都为n平方+1
}
}
public void open (int i,int j)
{
box[i][j]=1;
count_open++;
}
public boolean isopen(int i,int j)
{
return box[i][j]==1;
}
public boolean isfull(int i,int j)
{
return box[i][j]==0;
}
public int find(int p)
{
while(id[p]!=p)
{
p=id[p];
}
return id[p];
}
public boolean connected (int i,int j)
{
return find(i)==find(j);
}
public boolean quickconnected(int i,int j)
{
return id[i]==id[j];
}
public boolean percolates()//with no canshu
{
if(connected(top,bottom))//可以理解为顶行的根节点为top,底行为bottom,只要这两行中有任意两个点联通,则top和bottpom联通
{
return connected;
}
else
{
return unconnected;
}
}
public boolean quickpercolates()//with no canshu
{
if(quickconnected(top,bottom))//可以理解为顶行的根节点为top,底行为bottom,只要这两行中有任意两个点联通,则top和bottpom联通
{
return connected;
}
else
{
return unconnected;
}
}
public void union(int p,int q)
{
int i=find(p);
int j=find(q);
if(i==j)
{
return ;
}
if(sz[i]<sz[j])
{
id[i]=j;
sz[j]=sz[i]+sz[j];
}
else
{
id[j]=i;
sz[i]=sz[i]+sz[j];
}
}
public void quickfindunion(int p,int q)
{
int i=id[p];
int j=id[q];
if(i==j)
{
return;
}
int z;
for(z=0;z<id.length;z++)
{
if(id[z]==i)
{
id[z]=j;
}
}
}
public void quickunion(int p,int q)
{
int i=find(p);
int j=find(q);
if(i==j)
{
return ;
}
id[i]=j;
}
}
类二:
package 渗透;
import java.lang.Math;
public class Percolationstats //union——find加权
{
int N,T;
double[]sample=new double[100000];
double[]sample1=new double[100000];
double mean,stddev,lo,hi;
public Percolationstats(int N,int T)//
{
this.N=N;
this.T=T;
int total=N*N;
for(int times=0;times<T;times++)
{
Percolation per =new Percolation(N);
while(!per.percolates())//打开格点直到渗透
{
int i=(int )(Math.random()*N);
int j=(int )(Math.random()*N);
if(per.isfull(i, j))
{
per.open(i, j);//打开格点,把以前不开的打开
int p=0,q=0;
p=i*N+j;
if(i-1>=0)
{
q=p-N;
if(per.box[i-1][j]==1)
{
per.union(p, q);
}
}
if(j+1<N)
{
q=p+1;
if(per.box[i][j+1]==1)
{
per.union(p, q);
}
}
if(i+1<N)
{
q=p+N;
if(per.box[i+1][j]==1)
{
per.union(p, q);
}
}
if(j-1>=0)
{
q=p-1;
if(per.box[i][j-1]==1)
{
per.union(p, q);
}
}
}
}
sample[times]=1.0*per.count_open/total;//计算open格点与总格点的比值
}
}
public double mean(){//计算平均阈值
double sum=0;
for(int i=0;i<T;i++)
{
sum=sum+sample[i];
}
mean=sum/T;
return mean;
}
public double stddev() //几次的方差
{
double sig,sum=0;
for(int i=0;i<T;i++)
{
sum=sum+(sample[i]-mean)*(sample[i]-mean);
}
sig=sum/(T-1);
stddev=Math.sqrt(sig);
return stddev;
}
public double confidenceLo(){
lo=mean-1.96*stddev/Math.sqrt(T);//置信区间最小值
return lo;
}
public double confidenceHi()//置信区间最大值
{
hi=mean+1.96*stddev/Math.sqrt(T);
return hi;
}
}
package 渗透;
import java.lang.Math;
public class quick_find {//quick-find
int N,T;
double[]sample=new double[100000];
double[]sample1=new double[100000];
double mean,stddev,lo,hi;
public quick_find(int N,int T)//
{
this.N=N;
this.T=T;
int total=N*N;
for(int times=0;times<T;times++)
{
int p=0;
Percolation per =new Percolation (N);
while(!per.quickpercolates())//打开格点直到渗透
{
int i=(int )(Math.random()*N);
int j=(int )(Math.random()*N);
if(per.isfull(i, j))
{
per.open(i, j);//打开格点,把以前不开的打开
int q=0;
p=i*N+j;
if(i-1>=0)
{
q=p-N;
if(per.box[i-1][j]==1)
{
per.quickfindunion(p, q);
}
}
if(j+1<=N-1)
{
q=p+1;
if(per.box[i][j+1]==1)
{
per.quickfindunion(p, q);
}
}
if(i+1<=N-1)
{
q=p+N;
if(per.box[i+1][j]==1)
{
per.quickfindunion(p, q);
}
}
if(j-1>=0)
{
q=p-1;
if(per.box[i][j-1]==1)
{
per.quickfindunion(p, q);
}
}
}
}
sample[times]=1.0*per.count_open/total;//计算open格点与总格点的比值
}
}
public double mean(){//计算平均阈值
double sum=0;
for(int i=0;i<T;i++)
{
sum=sum+sample[i];
}
mean=sum/T;
return mean;
}
public double stddev() //几次的方差
{
double sig,sum=0;
for(int i=0;i<T;i++)
{
sum=sum+(sample[i]-mean)*(sample[i]-mean);
}
sig=sum/(T-1);
stddev=Math.sqrt(sig);
return stddev;
}
public double confidenceLo(){
lo=mean-1.96*stddev/Math.sqrt(T);//置信区间最小值
return lo;
}
public double confidenceHi()//置信区间最大值
{
hi=mean*1.96*stddev/Math.sqrt(T);
return hi;
}
}
package 渗透;
import java.lang.Math;
public class quickfindPercolationstats {//union-find
int N,T;
double[]sample=new double[100000];
double[]sample1=new double[100000];
double mean,stddev,lo,hi;
public quickfindPercolationstats(int N,int T)//
{
this.N=N;
this.T=T;
int total=N*N;
for(int times=0;times<T;times++)
{
Percolation per =new Percolation(N);
while(!per.percolates())//打开格点直到渗透
{
int i=(int )(Math.random()*N);
int j=(int )(Math.random()*N);
if(per.isfull(i, j))
{
per.open(i, j);//打开格点,把以前不开的打开
int p=0,q=0;
p=i*N+j;
if(i-1>=0)
{
q=p-N;
if(per.box[i-1][j]==1)
{
per.quickunion(p, q);
}
}
if(j+1<N)
{
q=p+1;
if(per.box[i][j+1]==1)
{
per.quickunion(p, q);
}
}
if(i+1<N)
{
q=p+N;
if(per.box[i+1][j]==1)
{
per.quickunion(p, q);
}
}
if(j-1>=0)
{
q=p-1;
if(per.box[i][j-1]==1)
{
per.quickunion(p, q);
}
}
}
}
sample[times]=1.0*per.count_open/total;//计算open格点与总格点的比值
}
}
public double mean(){//计算平均阈值
double sum=0;
for(int i=0;i<T;i++)
{
sum=sum+sample[i];
}
mean=sum/T;
return mean;
}
public double stddev() //几次的方差
{
double sig,sum=0;
for(int i=0;i<T;i++)
{
sum=sum+(sample[i]-mean)*(sample[i]-mean);
}
sig=sum/(T-1);
stddev=Math.sqrt(sig);
return stddev;
}
public double confidenceLo(){
lo=mean-1.96*stddev/Math.sqrt(T);//置信区间最小值
return lo;
}
public double confidenceHi()//置信区间最大值
{
hi=mean+1.96*stddev/Math.sqrt(T);
return hi;
}
}
package 渗透;
import java.util.*;
public class text {
public static void main (String [] args)
{
System.out .println("请输入N和T");
int N,T;
Scanner input=new Scanner(System.in);
N=input.nextInt();
T=input.nextInt();
int i,j;
for(i=0;i<5;i++)
{
long start,end,start1,end1,start2,end2;
start=System.currentTimeMillis();
quickfindPercolationstats p=new quickfindPercolationstats(N,T);
end=System.currentTimeMillis();
start1=System.currentTimeMillis();
Percolationstats q=new Percolationstats(N,T);
end1=System.currentTimeMillis();
start2=System.currentTimeMillis();
quick_find e=new quick_find(N,T);
end2=System.currentTimeMillis();
System.out .println("Result:");
System.out .println("mean ="+q.mean());
System.out .println("stddev ="+q.stddev());
System.out .println("confidenceLow ="+q.confidenceLo());
System.out .println("confidenceHigh ="+q.confidenceHi());
System.out .println("N ="+N+" T ="+T);
System.out .println("quickfind time ="+(end2-start2)+"ms\n");
System.out .println("union running time ="+(end-start)+"ms\n");
System.out .println("union running -quantime ="+(end1-start1)+"ms\n");
N=N*2;
}
}
}
实现