通过回溯解决数独(java)

时间:2015-12-12 23:52:58

标签: java recursion backtracking

public static int[][] solve(int[][] input){

        for (int i = 0; i < 9*9; i++){
            if(input[i / 9][i % 9] != 0){
                continue;
            }
            for (int j = 1; j <= 9; j++){
                    if(validNumber(input, i / 9, i % 9, j)){
                        input[i / 9][i % 9] = j;
                        solve(input);
                    }
            }
        }
        return input;
    }

无论初始情况如何,此方法都应通过回溯来解决(可解决的)数独谜题。它的工作原理如下:

给定一个数独谜题,它从每行的左上角迭代到2D数组的右下角。当已有数字时,它会被跳过。当存在零(空字段)时,它通过validNumber方法计算可能的值。第一个有效数字(从1到9)放在字段中,方法转到下一个字段。

在此算法中,该方法现在不会确定有效数字是否最终会使拼图无法解析。

我想改变它:

最后,当方法完成遍历整个2d数组的迭代时,如果数组的每个条目为零,则对其进行测试。

如果甚至有一个零,则整个算法必须到达第一个“有效”数字的位置。现在,下一个“有效”数字被放入,依此类推,直到没有零为止。算法结束。

我实施这个想法有些麻烦。在我看来,某个地方必须有一个其他for循环,或类似goto语句,但我不知道在哪里放它。

有什么建议吗?

3 个答案:

答案 0 :(得分:2)

我曾经实施过一次数独求解器。它比你的复杂一点,但是眨眼就解决了这个问题。 :)

你要做的是通过&#34; Brute Force&#34;解决数独游戏。并使用(尾部)递归。这意味着您试图通过迭代所有 9 81 可能的组合来解决问题。对81的力量来说,9是......它是一个很大的数字。所以你的方法需要永恒,但是你很快就会从尾递归中耗尽堆栈空间。

当我实施Sudoko时,它更直接了。它保留了一个9x9数组&#34;项目&#34;,其中每个项目是正方形中的值,以及一个代表候选项的9个布尔值的数组(true ==可行,false ==已淘汰)。然后它只是做了一个非递归的解决方案循环。

主循环将从找到仅有1个剩余候选的正方形的简单过程开始。然后,下一步将根据已分配的值进行简单的候选消除。然后它会用于更复杂的消除技术,例如X-Wing

答案 1 :(得分:1)

您的算法实际上并没有回溯。如果它可以向前移动,但当它意识到它被困在一个角落时它永远不会向后移动。这是因为它永远不会在堆栈中返回任何知识,并且它永远不会重置正方形。除非你真的很幸运,否则你的代码会让游戏板进入一个角落状态,然后打印出那个被逼迫的状态。要回溯,你需要将你设置的最后一个方块(让你走投无路)重置为零,这样你的算法就会知道继续尝试其他的东西。

为了理解回溯,我强烈推荐Steven Skiena撰写的一本名为The Algorithm Design Manual的书。我在准备SWE采访时读到了它,它确实提高了我对回溯,复杂性和图搜索的了解。本书的后半部分是75个经典算法问题的目录,数独就是其中之一!他对可以修剪搜索树和解决非常困难的拼图板的优化进行了有趣的分析。下面是我在阅读本章后很久以前写过的一些代码(可能不是我现行标准的高质量,但它有效)。我只是很快地阅读它,并在solveSmart方法中添加了solve布尔值,允许您打开或关闭其中一个优化,这样可以在解决“硬盘”时节省大量时间“类Sudoku棋盘(一个只有17个方格填写开始)。

public class Sudoku {

  static class RowCol {
    int row;
    int col;

    RowCol(int r, int c) {
      row = r;
      col = c;
    }
  }

  static int numSquaresFilled;
  static int[][] board = new int[9][9];

  static void printBoard() {
    for (int i = 0; i < 9; i++) {
      for (int j = 0; j < 9; j++) {
        System.out.print(" " + (board[i][j] == 0 ? " " : board[i][j]) + " ");
        if (j % 3 == 2 && j < 8)
          System.out.print("|");
      }
      System.out.println();
      if (i % 3 == 2 && i < 8)
        System.out.println("---------|---------|---------");
    }
    System.out.println();
  }

  static boolean isEntireBoardValid() {
    for (int i = 0; i < 9; i++) {
      for (int j = 0; j < 9; j++) {
        if (!isBoardValid(i, j)) {
          return false;
        }
      }
    }
    return true;
  }

  static boolean isRowValid(int row) {
    int[] count = new int[9];
    for (int col = 0; col < 9; col++) {
      int n = board[row][col] - 1;
      if (n == -1)
        continue;
      count[n]++;
      if (count[n] > 1)
        return false;
    }
    return true;
  }

  static boolean isColValid(int col) {
    int[] count = new int[9];
    for (int row = 0; row < 9; row++) {
      int n = board[row][col] - 1;
      if (n == -1)
        continue;
      count[n]++;
      if (count[n] > 1)
        return false;
    }
    return true;
  }

  static boolean isSquareValid(int row, int col) {
    int r = (row / 3) * 3;
    int c = (col / 3) * 3;
    int[] count = new int[9];
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        int n = board[r + i][c + j] - 1;
        if (n == -1)
          continue;
        count[n]++;
        if (count[n] > 1)
          return false;
      }
    }
    return true;
  }

  static boolean isBoardValid(int row, int col) {
    return (isRowValid(row) && isColValid(col) && isSquareValid(row, col));
  }

  static RowCol getOpenSpaceFirstFound() {
    for (int i = 0; i < 9; i++) {
      for (int j = 0; j < 9; j++) {
        if (board[i][j] == 0) {
          return new RowCol(i, j);
        }
      }
    }
    return new RowCol(0, 0);
  }

  static RowCol getOpenSpaceMostConstrained() {
    int r = 0, c = 0, max = 0;
    int[] rowCounts = new int[9];
    int[] colCounts = new int[9];
    for (int i = 0; i < 9; i++) {
      for (int j = 0; j < 9; j++) {
        if (board[i][j] != 0)
          rowCounts[i]++;
        if (board[j][i] != 0)
          colCounts[i]++;
      }
    }

    int[][] squareCounts = new int[3][3];
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        int count = 0;
        for (int m = 0; m < 3; m++) {
          for (int n = 0; n < 3; n++) {
            if (board[(i * 3) + m][(j * 3) + n] != 0)
              count++;
          }
        }
        squareCounts[i][j] = count;
      }
    }

    for (int i = 0; i < 9; i++) {
      for (int j = 0; j < 9; j++) {
        if (board[i][j] == 0) {
          if (rowCounts[i] > max) {
            max = rowCounts[i];
            r = i;
            c = j;
          }
          if (colCounts[j] > max) {
            max = rowCounts[j];
            r = i;
            c = j;
          }
        }
      }
    }
    return new RowCol(r, c);
  }

  static boolean solve() {
    if (81 == numSquaresFilled) {
      return true;
    }

    boolean solveSmart = true;
    RowCol rc = solveSmart ? getOpenSpaceMostConstrained() : getOpenSpaceFirstFound();
    int r = rc.row;
    int c = rc.col;
    for (int i = 1; i <= 9; i++) {
      numSquaresFilled++;
      board[r][c] = i;
      if (isBoardValid(r, c)) {
        if (solve()) {
          return true;
        }
      }
      board[r][c] = 0;
      numSquaresFilled--;
    }
    return false;
  }

  public static void main(String[] args) {

    // initialize board to a HARD puzzle
    board[0][7] = 1;
    board[0][8] = 2;
    board[1][4] = 3;
    board[1][5] = 5;
    board[2][3] = 6;
    board[2][7] = 7;
    board[3][0] = 7;
    board[3][6] = 3;
    board[4][3] = 4;
    board[4][6] = 8;
    board[5][0] = 1;
    board[6][3] = 1;
    board[6][4] = 2;
    board[7][1] = 8;
    board[7][7] = 4;
    board[8][1] = 5;
    board[8][6] = 6;
    numSquaresFilled = 17;

    printBoard();
    long start = System.currentTimeMillis();
    solve();
    long end = System.currentTimeMillis();
    System.out.println("Solving took " + (end - start) + "ms.\n");
    printBoard();
  }
}

答案 2 :(得分:0)

最终validNumber()方法不会返回任何数字,因为没有可能性,这意味着之前的选择之一是不正确的。试想一下,算法是以空网格开始的(显然这个难题可以解决 1 )。

解决方案是保留树的可能选择,如果某些选择不正确,那么只需从树中删除它们并使用下一个可用选项(或者如果没有选择则退回树的更高级别)在这个分支)。如果有的话,该方法应该找到解决方案。 (实际上这是我不久前实现我的数独求解器的方法。)

1 恕我直言,有3种不同的数独:

  1. &#34;真&#34;纠正具有单一唯一完整解决方案的数独;

  2. 模糊的数独,有多个不同的完整解决方案,例如一个只有7个不同数字的谜题,所以它至少有两个不同的解决方案,通过交换第8和第9个数字而不同;

  3. 不正确的数据,没有完整的解决方案,例如有两行或多次出现相同数字的行。

  4. 使用此定义,求解器算法应该:

    1. 证明没有解决方案;

    2. 返回满足初始网格的完整解决方案。

    3. 对于&#34; true&#34;数独,结果是&#34;真&#34;按定义解决方案。在模糊数独的情况下,结果可能根据算法而不同。空网格是模糊数独的最终例子。