[关闭]
@w1024020103 2017-04-27T10:51:11.000000Z 字数 4401 阅读 1150

Homework 2: Disjoint Sets and Percolation

CS61B


看了Percolation, Part 2 : Introduction,因为之前草草刷Algorithm Part 1的时候做过,所以记得大概是干什么的。

第一次几乎全是brute force, 没在意performance requirements,想先实现再慢慢修改。但运行PercolationVisualizer发现出错:

1.JPG-31.3kB

发现自己犯了个错,我写的open方法里,有一部分代码是当一个block被open之后,根据情况来决定要不要跟上下左右的block来union. 其中比如col + 1< gridSize - 1 的情况下,就要和右边如果已经open的block union. 但我居然写成:

  1. while(col + 1< gridSize - 1 ) {
  2. if(isOpen(row, col + 1)) {
  3. uf.union(xyTo1D(row, col), xyTo1D(row, col + 1));
  4. }
  5. }

这种写法会使得while变成死循环, 很简单地用一个&&来结合成一个if语句就解决问题了:

  1. if(col + 1< gridSize - 1 &&isOpen(row, col + 1)) {
  2. uf.union(xyTo1D(row, col), xyTo1D(row, col + 1));
  3. }

但visualizer出来的结果还是对不上。

我的结果:

2.jpg-37.5kB

答案的结果:
3.JPG-56.9kB

修改了一下isFull, 还是有问题:

4.JPG-58kB

换了一个更简单的输入,尝试找到问题所在:

我的结果:

5.JPG-35kB

正确答案:

6.JPG-28.8kB

果然数据输入较小的时候,更容易清楚地找到问题所在。我在main方法里依次检查了可以block的isFull和isConnected状态,最后debug发现了问题:

我的open里原先这样写的:

  1. public void open(int row, int col) {
  2. if(row < 0 || row > gridSize - 1 || col < 0 || col > gridSize - 1){
  3. throw new IndexOutOfBoundsException();
  4. }
  5. if(isGridOpened[row][col]){
  6. return;
  7. } else{
  8. isGridOpened [row][col] = true;
  9. }
  10. //if left,right,top,bottom block is open, connect them.
  11. if (col + 1< gridSize && isOpen(row, col + 1)) {
  12. uf.union(xyTo1D(row, col), xyTo1D(row, col + 1));
  13. }
  14. if (col - 1 > 0 && isOpen(row, col - 1)) {
  15. uf.union(xyTo1D(row, col), xyTo1D(row, col - 1));
  16. }
  17. if( row + 1 < gridSize && isOpen(row + 1, col)){
  18. uf.union(xyTo1D(row,col),xyTo1D(row + 1, col));
  19. }
  20. if (row - 1 > 0 && isOpen(row - 1, col)) {
  21. uf.union(xyTo1D(row, col), xyTo1D(row - 1, col));
  22. }
  23. }

问题就处在两行:

  1. if (col - 1 > 0 && isOpen(row, col - 1)) {
  2. uf.union(xyTo1D(row, col), xyTo1D(row, col - 1));
  3. }
  4. if (row - 1 > 0 && isOpen(row - 1, col)) {
  5. uf.union(xyTo1D(row, col), xyTo1D(row - 1, col));
  6. }

这里注意,index是可以等于0,但永远不能等于gridSize的,改为:

  1. if (col - 1 >= 0 && isOpen(row, col - 1)) {
  2. uf.union(xyTo1D(row, col), xyTo1D(row, col - 1));
  3. }
  4. if (row - 1 >= 0 && isOpen(row - 1, col)) {
  5. uf.union(xyTo1D(row, col), xyTo1D(row - 1, col));
  6. }

这下初步完成了功能实现,下面进行runtime的优化。

正确实施:

7.JPG-87.6kB

注意到Performance requirements里讲到Your numberOfOpenSites() method must take constant time. 于是换掉了原来那种N2的扫描全部blocks然后统计isOpened个数的方式,添加了一个field variable private int numOfOpenSite; 然后每一次open这个数字自加一,最终达到constant time执行numberOfOpenSites()方法:

  1. public int numberOfOpenSites(){
  2. return numOfOpenSites;
  3. }

Performance requirements里还有一句话:all methods should take constant time plus a constant number of calls to the union-find methods union(), find(), connected(), and count().

我的isFull()里面有这么短代码:

  1. for(int i = 0; i < gridSize; i++) {
  2. if (isOpen(row, col) && isOpen(0, i) && uf.connected(xyTo1D(row,col), i)) {
  3. return true;
  4. }
  5. }

显然有Linear time次call了connected(),不满足要求。所以要用到instruction视频里说的virtual sites.

我修改了this.uf = new WeightedQuickUnionUF( N * N + 2 );来新增两个site. 一个top virtual site (N*N), 一个bottom virtual site(N*N -1). 然后在open()里特意留意第一行和最后一行的情况,如果要打开的是这两行的block, 最后要分别与top virtual site或者bottom virtual site 实现connect.

  1. public void open(int row, int col) {
  2. if(row < 0 || row > N - 1 || col < 0 || col > N - 1){
  3. throw new IndexOutOfBoundsException();
  4. }
  5. if(isGridOpened[row][col]){
  6. return;
  7. } else{
  8. isGridOpened [row][col] = true;
  9. numOfOpenSites += 1;
  10. if(row == 0){
  11. uf.union(xyTo1D(row,col), N*N);
  12. }
  13. if(row == N -1 ){
  14. uf.union(xyTo1D(row,col), N*N +1);
  15. }
  16. }
  17. //if left,right,top,bottom block is open, connect them.
  18. if (col + 1< N && isOpen(row, col + 1)) {
  19. uf.union(xyTo1D(row, col), xyTo1D(row, col + 1));
  20. }
  21. if (col - 1 >= 0 && isOpen(row, col - 1)) {
  22. uf.union(xyTo1D(row, col), xyTo1D(row, col - 1));
  23. }
  24. if( row + 1 < N && isOpen(row + 1, col)){
  25. uf.union(xyTo1D(row,col),xyTo1D(row + 1, col));
  26. }
  27. if (row - 1 >= 0 && isOpen(row - 1, col)) {
  28. uf.union(xyTo1D(row, col), xyTo1D(row - 1, col));
  29. }
  30. }

最后还剩下一个由virtual sites引发的backwash问题:

8.JPG-62.1kB

9.jpg-40kB

为了避免backwash,可以多增加一个WeightedQuickUnionUF对象,这个对象size只有N*N + 1,也就是只有一个virtual top site. 这样就可以避免因为Bottom virtual site引发的问题。 在isFull()方法里就只需要看这个block是不是跟这个new WeightedQuickUnionUF的virtual top site是connected就可以。相应地对每个方法里的union要添加新的UF对象。之前的那个UF对象可以保证判断percolates().

  1. private WeightedQuickUnionUF uf;
  2. private WeightedQuickUnionUF noBackWash;
  3. public Percolation(int N) {
  4. if( N <= 0){
  5. throw new IllegalArgumentException();
  6. }
  7. this.isGridOpened = new boolean[N][N];
  8. this.N = N;
  9. for( int i = 0; i < N; i++){
  10. for(int j = 0; j < N; j++) {
  11. this.isGridOpened[i][j] = false;
  12. }
  13. }
  14. this.numOfOpenSites = 0;
  15. this.uf = new WeightedQuickUnionUF( N * N + 2 );
  16. this.noBackWash = new WeightedQuickUnionUF( N * N + 1);
  17. }

做了PercolationStats.java,刚开始的时候发现所有threshold数组的元素都等于0.0,后来加了一个cast到double后才显示成正常的数.因为那里默认是int,而结果是小于零的小数,若为int类型就会直接舍掉小数部分,结果就会显示为0.

  1. public PercolationStats(int N, int T) {
  2. if( N <= 0 || T <= 0){
  3. throw new java.lang.IllegalArgumentException();
  4. }
  5. Percolation p = new Percolation(N);
  6. this.threshold = new double [T];
  7. this.numOfExperiment = T;
  8. for(int i = 0; i < T; i++){
  9. //uniform(int n) returns a random integer uniformly in [0, n).
  10. while(!p.percolates()){
  11. p.open(StdRandom.uniform(N),StdRandom.uniform(N));
  12. }
  13. this.threshold[i] = (double) p.numberOfOpenSites() / (N * N);
  14. }
  15. }

这个hw我在上coursera上的Princeton Part 1的时候就已经做过。现在才觉得当时完全不懂这道题在干什么,而这一次是真正理解了并完全独立做的。时间间隔只是4个月左右,自己的水平还是相当不同了,看同样的题目完全是两种feel。继续加油,说明编程入了门就好懂,一直不开始才是最浪费时间的。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注