在N Rooks问题中如何避免n = 8的stackoverflow错误

时间:2019-05-01 15:25:42

标签: java algorithm recursion backtracking

我想使用最大 N = 8 的递归来解决 N x N 板中的 N 问题。 。我的代码适用于 N = 2 3 4 5 6 7 。但是,当 N = 8 时,它给出了很多可能的结果,从 1 0 0 0 0 0 0 0 0 的第一行开始,然后给出 > stackoverflow错误 ,然后再检查其他可能的结果,从第一行 0 1 0 0 0 0 0 0 开始。

我了解一般递归,例如斐波那契数列阶乘等,我可以对其进行跟踪。然后,我遇到了一种新的递归形式,称为回溯递归。然后,我努力学习这种形式的递归背后的逻辑,并阅读一些伪代码算法。从根本上说,这种递归形式在我看来比正常递归要难一些。

public class NRooks {
/**
 * In this code r = which row, c = which column.
 * lastY method just returns column c of last placed rook in
 * a given row r in order to remove it.
 * row.length, col.length, board.length have no special meaning. They all
 * equal to the dimension of board N.
 * main() method always initiates first row(r = 0). Therefore in main()
 * method r remains 0 and c changes as you can see in putRook(0, i). 
 * So solve() method always begins from second row(r = 1).
 */

private static int found = 0;
private static int[][] board;
private static int[] row;
private static int[] col;

public static void putRook(int r, int c) {
    board[r][c] = 1;
    row[r]  = 1;
    col[c]  = 1;
}

public static void removeRook(int r, int c) {
    board[r][c] = 0;
    row[r]  = 0;
    col[c]  = 0;
}

public static boolean isValid(int r, int c) {
    if (row[r] == 0 && col[c] == 0) return true;
    return false;
}

public static void showBoard() {
    for (int r = 0; r < board.length; r++) {
        for (int c = 0; c < board.length; c++) {
            System.out.print(board[r][c] + " ");
        }
        System.out.println();
    }
    System.out.println();
}

public static int lastY(int r) {
    for (int j = 0; j < board.length; j++) {
        if (board[r][j] == 1) return j;
    }
    return -1;
}


public static boolean solve(int r, int c) {
    int last;

    if (r == 0) return false;

    if (r == col.length) {
        found++;
        /**
         * When I dont include below printline statement my code 
         * works fine until N = 7 then gives SO error.
         * But When I include this print statement in order
         * to print number of results my code works fine until
         * N = 6 then gives SO error
         */
        //System.out.println("Found: " + found);
        showBoard();
        r--;
        last = lastY(r);
        removeRook(r, last);
        c = last + 1;
    }

    for (int j = c; j < row.length; j++) {
        if (isValid(r, j)) {
            putRook(r, j);
            return solve(r + 1, 0);
        }
    }

    last = lastY(r - 1);
    removeRook(r - 1, last);
    return solve(r - 1, last + 1);
}

public static void main(String[] args) {
    int n = Integer.parseInt(args[0]);
    board = new int[n][n];
    row = new int[n];
    col = new int[n];

    for (int i = 0; i < row.length; i++) {
        boolean finished; // not important
        putRook(0, i);
        finished = solve(1, 0);
        if (finished) System.out.println("============"); // ignore this too
    }
}
}

Stackoverflow 指向包含对 solve()方法的递归调用的行。

注意:我只知道 C ,例如Java语法和基本数据抽象。我在 Java 级别上编写了此代码。

我想解决这个问题,我自己要解决N个皇后问题。 因为在数学和算法上都存在许多针对这些问题的解决方案。而且,我现在对高级 Java 数据抽象东西不感兴趣。
我只想在

之类的代码段上提供一些建议
  • 您的回溯算法效率不高。 (太直率了)
  • 您需要使用一些 Java 数据抽象方法来有效解决此问题。
  • 您需要使用另一种形式的递归,例如 tail recursion (我也听说过。)
  • ....

2 个答案:

答案 0 :(得分:1)

为什么会出现堆栈溢出错误的主要问题是递归的结构方式。在solve方法中调用main的那一刻,它会不断递归。实际上,它的所有调用都形成了一个单列的“深层调用”链。对于n = 7,有3193个嵌套调用(我添加了一个计数器来检查)。对于n = 8,它会在我的计算机上的堆栈溢出之前执行大约5k递归调用-我猜默认情况下堆栈大小很小。

因此,要使它适用于更高的n值,您需要以一种不会将所有递归调用作为单个链执行的方式来重构递归。我可以说您当前的解决方案并不是真正的回溯,因为它从来没有真正回溯。让我说明回溯对一个简单问题的含义。假设您要以编程方式打印所有长度为n = 3(“ 000”至“ 111”)的二进制字符串,而不必依赖于知道n的值。一个实现可能是这样的:

def build_binary_string(current_prefix, chars_left):
  if chars_left == 0:
    print current_prefix
    return
  build_binary_string(current_prefix + 'a', chars_left - 1)
  build_binary_string(current_prefix + 'b', chars_left - 1)

build_binary_string("", 3)

有趣的事情(回溯!)发生在使用参数(“ 00”,1)调用build_binary_string的时刻:

  • build_binary_string("000", 0)被调用,显示“ 000”并立即返回
  • 我们回到build_binary_string("00", 1)函数调用中,即将执行build_binary_string(current_prefix + 'b', chars_left - 1)
  • build_binary_string("001", 0)被调用,显示“ 001”并立即返回

当控制流从build_binary_string("000", 0)返回到build_binary_string("00", 1)时,它选择进行另一个函数调用是回溯。请注意,递归深度从未超过3。

答案 1 :(得分:0)

由于我没有某些方法,因此我无法测试您的代码,但是int j = c应该是int j = r吗?

for (int j = c; j < row.length; j++) {
    if (isValid(row, col, r, j)) {
        putRook(b, row, col, r, j);
        return solve(b, row, col, r + 1, 0);
    }
}

在此行内,您要将0传递给c,然后在for循环条件中声明j = c,因此j

   return solve(b, row, col, r + 1, 0);

编辑:我现在看到在上面的if块中声明了c,但是如果if块没有执行,则此后应该是一个无限循环。也许检查一下r == col.length是否正确执行。