【问题标题】:My loop never ends我的循环永远不会结束
【发布时间】:2017-05-21 20:21:54
【问题描述】:

我的程序有问题,它应该解决数独并在完成后打印,但问题是,程序看起来永远不会结束,打印已解决的数独后会出错。

这是程序正在执行的循环: 它也不应该打印字段,如果我在开始时删除System.out.print(f+"\n");,它只会打印错误。 代码:

        public class Sudoku {


  public static void main(String[] args) throws SolvedException {
    Field field = new Field();
    field.fromFile("test1.txt");
    SudokuSolver solver = new SudokuSolver();

    solve(field, 0, 0, solver);

    System.out.println(field);

  }




public static void solve(Field f, int i, int j, SudokuSolver solver) {

    System.out.print(f+"\n");

    if ( j >= Field.SIZE) {

        //we are done (return true now!)
        solver.done=true;
        return;

    } 

    if (f.isEmpty(i, j)) {

        for (int val = 1; val <=9; val++) {

            if (f.tryValue(val, i, j)){

                if (j>=Field.SIZE-1){

                    solve (f, i+1, 0, solver);

                    if ( solver.done ) {

                        // This halts the loop here:
                        return;
                    }

                    f.clear(i, j);

                } else {

                    solve(f,i,j+1, solver);

                    if ( solver.done ) {

                        // This halts the loop here:
                        return;
                    }

                    f.clear(i, j);

                }

            }
        }

    } else if (j>=Field.SIZE-1) {

        solve(f,i+1,0, solver);

    } else {

        solve(f,i,j+1, solver);

    }
}
}

数独解法

public class SudokuSolver{

    /* Set true when the solve is done */
    public boolean done;

}

错误:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 9
    at Field.isEmpty(Field.java:101)
    at Sudoku.solve(Sudoku.java:30)
    at Sudoku.solve(Sudoku.java:38)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:67)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:38)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:38)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:38)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:67)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:67)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:38)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:38)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:71)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.solve(Sudoku.java:50)
    at Sudoku.main(Sudoku.java:9)

任何想法如何停止循环并打印正确的字段? System.out.println(field) 在解决方法中不是这个?

【问题讨论】:

  • 鉴于throws SolvedException,它可能应该在// we are done 部分中设置为throw new SolvedException();。值得注意的是,它是一个bad idea to use exceptions to control the flow 之类的。另一个注意事项是大量的非尾递归也可能导致随机堆栈溢出异常。
  • 简而言之,递归就是函数调用自身的地方。每次发生这种情况时,您都会得到另一个“堆栈帧”——基本上所有函数使用的局部变量都被存储了。如果一个函数多次调用自己,堆栈就会变得太大并且“溢出”。尾递归是一种优化,本质上堆栈帧可以被“重用”,因为编译器注意到局部变量不再使用,允许无限递归发生。另请参阅 this answer 以获得更深入的比较。
  • @Mercy 在发布好答案并解决您的问题后,不要删除“请删除此问题”的问题正文。如果你以这种方式编辑它会混淆其他人。
  • @Mercy 那么你不应该首先发布这个问题。请先熟悉本网站的运作方式。
  • 我不明白你的问题是什么。 taskinoor 告诉您,不允许您删除问题的重要部分,并且您继续这样做。我已将此问题标记为版主锁定此问题并防止您进一步操纵。

标签: java loops recursion backtracking sudoku


【解决方案1】:

某处试图访问不存在的数组上部元素。尝试调试并找到。

可能 val 是要寻找的关键。

我假设 for (int val = 1; val ; val++) 应该 for (int val = 1; val ; val++)

【讨论】:

  • 您似乎也是第一次使用stackoverflow :)。如果您选择我的答案作为解决方案,我会很高兴。所以,我会收集我的积分;)
  • 我没时间帮你完成整个任务,但我解决了你的错误。
【解决方案2】:

永不终止的递归

(在这种情况下,它会在出错时终止)。

这部分代码需要实际做一些事情来表明它已经完成:

if ( j >= Field.SIZE) {

    //we are done

}

否则,循环只会继续运行,而不知道它应该停止(缩短):

for (int val = 1; val <=9; val++) {

     ...
     
     solve (f, i+1, 0); // Maybe this call is 'done', but this loop will keep going

     ...
}

因此,基于throws SolvedException,您的代码应该在那里引发异常:

if ( j >= Field.SIZE) {

    //we are done
    throw new SolvedException();
    
}

但这是一个坏主意。异常不是用于控制代码流 - 它们适用于completely unexpected situations

表示已完成

相反,我们需要通过某种方式知道代码何时达到“完成”条件。在经典递归中,这是通过返回一些东西来执行的。我们只想知道它是否完成,bool 处理得很好:

// Type changed to bool, removed throws:

public static bool solve(Field f, int i, int j) {

    System.out.print(f+"\n");

    if ( j >= Field.SIZE) {

        //we are done (return true now!)
        return true;

    } 

    if (f.isEmpty(i, j)) {
        
        for (int val = 1; val <=9; val++) {
            
            if (f.tryValue(val, i, j)){

                if (j>=Field.SIZE-1){
                    
                    if( solve (f, i+1, 0) ){
                        // This halts the loop here:
                        return true;
                    }

                    f.clear(i, j);
                    
                } else {
                    
                    if( solve(f,i,j+1) ){
                        // This halts the loop here:
                        return true;
                    }

                    f.clear(i, j);

                }

            }
        }
        
    } else if (j>=Field.SIZE-1) {
        
        // (Side note: This one is tail recursion)
        return solve(f,i+1,0);
        
    }
    
    // (Side note: This one is tail recursion)
    return solve(f,i,j+1);

}

在被调用者中,你也有这个:

try {
  solve(field, 0, 0);
} 
catch (SolvedException e) { }

你也想换掉:

if( solve(field, 0, 0) ){

    // It was solved!
    
}

改为返回 void

您提到您仍想返回void。好的,所以,我们需要在其他地方跟踪“完成”状态 - 例如在我们称之为SudokuSolver的某个对象内部:

public class SudokuSolver{
    
    /* Set true when the solve is done */
    public bool done;
    
}

使用它会使代码看起来更像这样:

// Type changed to void, added our solver arg:

public static void solve(Field f, int i, int j, SudokuSolver solver) {
    
    System.out.print(f+"\n");

    if ( j >= Field.SIZE) {

        //we are done (return true now!)
        solver.done=true;
        return;
        
    } 
    
    if (f.isEmpty(i, j)) {
        
        for (int val = 1; val <=9; val++) {
            
            if (f.tryValue(val, i, j)){

                if (j>=Field.SIZE-1){
                    
                    solve (f, i+1, 0, solver);
                    
                    if ( solver.done ) {
                        
                        // This halts the loop here:
                        return;
                    }

                    f.clear(i, j);
                    
                } else {
                    
                    solve(f,i,j+1, solver);
                    
                    if ( solver.done ) {
                        
                        // This halts the loop here:
                        return;
                    }

                    f.clear(i, j);

                }

            }
        }
        
    } else if (j>=Field.SIZE-1) {
        
        solve(f,i+1,0, solver);
        
    } else {
        
        solve(f,i,j+1, solver);
        
    }
    
}

调用站点现在看起来像这样:

SudokuSolver solver=new SudokuSolver();

solve(field, 0, 0, solver);

if ( solver.done ) {
    // It was solved!
}

【讨论】:

  • @Mercy 这更有可能是因为实际的算法并没有按照你的意愿去做 - 这个答案只关注停止循环:)
  • @Mercy 我又做了一些修改 - 请参阅底部的部分(throws new .. 也应该是 throw new,但请避免使用它如答案中所述)。
  • @Mercy 你用它来学习 Java 吗?在这种情况下,更有理由避免使用糟糕的技术:)
  • @Mercy 我添加了一种技术,它仍然返回 void - 因为它是为了学校,所以不要使用异常:)
  • @Mercy Java 类必须始终放在它们自己的文件中,所以可能是这样 - 我还没有测试过(我没有你的 Field 类,所以我不能) ;它更多地只是对常用技术的演示:)
猜你喜欢
  • 2011-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多