Assignment 1 | Percolation | Princeton Algorithms
1.作业内容
渗滤系统,N*N的方格,每一个格子有p的几率是open的,1-p的几率是blocked。当p是多少的时候,这个系统几乎一定是可以渗滤(即能从第一行通到最后一行)的呢?可以通过并查集实现该系统,大量计算机实验来确定p。
实现渗滤系统,进行实验来确定p。
需要两个文件,Percolation.java和PercolationStas.java。
在Percolation.java中,实现渗滤系统。主要功能有:初始化一个全部封闭的渗滤系统,打开一个格子,判断格子是否打开,判断格子是否full(上图中淡蓝色已经与顶部连接的部分),得到打开格子的数量,判断系统是否已经渗滤。
在PercolationStas.java中,实现统计部分。主要功能有:从命令行参数中获取渗滤系统边长和实验次数,进行多次实验,并求出(刚好渗滤时打开格子的数量/格子总数)的平均数、标准差和置信区间。当实验次数比较大,达到几百上万时,每次运行得到的平均数几乎是一模一样的。
2. 简要分析
创建一个二维数组grid[n][n]作为网格,再创建一个WeightedQuickUnionUF作为记录连接情况。同时需要顶部和底部两个虚节点,分别连接顶部节点和底部节点。所以WeightedQuickUnionUF中数组的长度需要n*n+2个。在open一个新节点时,除了要让其对应数组的值变为true,还需要将它与其上下左右的开放节点相连。最后判断是否渗滤时,需要判断顶部和底部虚节点是否已经相连。
3. 完成过程
(1)第一次修改
在第一次提交前,我已经能够打印出正确的平均数、标准差和置信区间的值,但是第一次提交狠狠打脸,只有5/100分T T 这说明,虽然在正常运行下程序可能能够给出预期结果,但是,还有一些特殊情况没有考虑到,并且修饰符等使用不到位,而且优化代码的结构可以大量减少内存消耗,这些都是需要优化和修改的。
下面是第一次修改的内容:
- 不变性:当实例变量的指针不再更改,可以设置为final。The private instance (or static) variable ‘uf’ can be made ‘final’; it is initialized only in the declaration or constructor. (uf指向一个对象)
- 注释//或/*之后要加一个空格
- 常用的常数需要设置为字段,static final
在改完这些问题后,才能正常运行其他test。
(2)第二次修改
37/100
- isFull方法的概念理解错了。修改。
- 抛出异常时边界没有考虑完全,例如输入边长为负。
- 构造函数也不要忘记抛出异常,输入参数可能不合理。
- 没有考虑到边长为1的情况。如果边长为1,则一开始就和首尾联通,满足渗滤。所以要单独考虑这个情况。
- 之前把重复做实验的过程写在了main函数里,其实可以直接写在PercolationStas的构造函数里,在main函数只要读取边长n和实验次数trails,并初始化PercolationStas对象即可得到实验结果,对使用者来说更方便,并且能够节省许多内存,这一点的原因与下面两点有关。
- PercolationStas内存超出限制。一开始我在写这个类的时候,使用一个实例变量res[trails]来存储每次试验结果,再在mean()方法中使用StdStats.mean(res)方法计算平均数,置信区间和标准差同理。但是这样会浪费巨大的空间!可以将res[trails]作为本地变量,在构造函数中初始化,在构造函数中完成平均值等的计算,只需要将平均值等作为实例变量保存起来即可,调用完构造函数,res[trails]就可以被回收了。
如果使用res[trails]作为实例变量,那么只要初始化PercolationStas,系统就会分配一个这么长的数组,并且一直保存,非常浪费。 - 同上,也没有必要将Percolation对象作为实例变量,只需要在构造函数中使用即可。
(3)第三次修改
59/100
- 平均值等实例变量只会被赋一次值,所以可以声明为final。
- 忘记考虑isFull必须同时也是isOpen了。
最终:94/100(80分算过)
github
- 待解决的问题:backwash(理解为反洗)
这个问题比较复杂,一时也没有想好,作业要求里也不强求。主要还是isFull的问题。一旦系统渗滤,和底部联通的也就会被认为是isFull了。(因为我的isFull写法是判断格子和顶部是否联通,如果渗滤了顶部和底部也就联通了)
4. 补充知识:printf
-
制表符\t
制表符\t输出的时候,会移动输出光标,实现对齐效果。因此可以在输出的对应位置,增加\t来实现对齐。但有个缺点:要求每行相同列输出占用空间差别不可以太大。(若输出的是数字信息,可以直接将log信息copy到excel表格中,能很好地统计数字数值信息) -
加入占用宽度控制数字
使用printf格式化输出时,每个控制字符可以写成%nC的形式,如%10d, %12f, %5c, %20s等等,其对应的是不足部分左侧补空格,实现右对齐效果;若要不足部分右侧补空格,只需要在宽度字符前加-符号即可,如%-12f,此时实现的是左对齐效果。 -
在“”中输入特殊字符需要转义字符。例如,打印%需要写%%。还能够:
System.out.printf("%-10s","abc"); //输出10列,左对齐(-号表示左对齐)
System.out.printf("%8d",23); //输出8列, 右对齐
-
表4-4 格式化转换符
格式化转换符定义
%b 无符号二进制整数
%c 字符
%d、i 十进制整数
%e 科学计数法浮点数
%E 使用大写字母E 的科学计数法浮点数
%f、%F 浮点数
%g 使用e 或f 转换符的浮点数,取其最小宽度
%G 使用e 或f 转换符的浮点数,取其最大宽度
%id、%D 长整型十进制数
%lu、%U 无符号长整型十进制数
%lo、%O 长整型八进制数
%p 指针(十六进制数)
%s 字符串
%u 无符号的十进制数
%x 十六进制数
%X 使用大写字母X 的十六进制数
%lx 长整型十六进制数
%% 打印百分号实量 -
表4-5 标记修饰符
转换符定 义
%- 左对齐修饰符
%# 如果是八进制数,则在显示时带上前导0;如果是十六进制数,则在显示时带有前缀0x
%+ 对于使用d、e、f 和g 的转换符,显示其整数部分,并显示正负号+、-
%0 把显示内容中的空白部分以0 补足
%number 最大字段宽度。譬如,若number 为6(如%6d),说明最大字段宽度是6
%.number 指定浮点数的精度。例如,%.2f 表示小数点后两位 ;%8.2 表示最大字段宽度为8,并
精确到小数点后两位