【问题标题】:N-Queens puzzle solution - cannot understandN-Queens 谜题解法——看不懂
【发布时间】:2017-01-30 22:50:47
【问题描述】:

我正在阅读 SICP 上 N-Queens 谜题的解决方案说明,但我无法理解其中的大部分内容。这是解决方案:

解决难题的一种方法是全面工作,放置一个 每一列的皇后。一旦我们放置了 k - 1 个皇后,我们必须放置 第 k 个皇后处于不检查任何皇后的位置 已经在板上了。我们可以递归地制定这种方法: 假设我们已经生成了所有可能的序列 将 k - 1 个皇后放在棋盘前 k - 1 列的方法。 对于这些方式中的每一种,通过以下方式生成一组扩展的位置 在第 k 列的每一行放置一个皇后。现在过滤这些, 只保留第 k 列中皇后所在的位置 相对于其他皇后来说是安全的。这产生了序列 在前 k 列中放置 k 个皇后的所有方法。通过继续这个 过程中,我们将产生的不仅是一种解决方案,而是所有解决方案 谜题。

假设一个 8 x 8 棋盘看起来像这样:我的眼睛被毁坏了,所以我不能使用图片。 0 表示没有女王,1 表示女王。

0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

全面工作,在每列中放置一个女王。

我的理解是垂直读取列,水平读取行。文字是这样的意思吗?

1 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 1 0 0
0 0 0 0 1 0 0 0
0 0 0 1 0 0 0 0
0 0 1 0 0 0 0 0
0 1 0 0 0 0 0 0

我在每列中放置了一个皇后,但没有指定行,但由于这是递归完成的,我假设我已经生成了两个皇后不相互检查的位置方式。

假设我们已经生成了所有可能的序列 将 k - 1 个皇后放在棋盘前 k - 1 列的方法。

假设 k = 1。所以 1-1 = 第 0 列有一种生成位置的方法,因为它是一个空板。

对于这些方式中的每一种,通过以下方式生成一组扩展的位置 在第 k 列的每一行放置一个皇后。

我对第 0 列的解决方案是 1 方式,但我完全不知道以下是什么意思。

通过在每一行放置一个皇后来生成一组扩展的位置 第 k 列。

“生成一组扩展的位置”并在列的每一行放置一个皇后是什么意思?如果k = 1是这样吗?

1 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0

但是所有的皇后都不安全,因为它们都在同一列,对吧?

我完全不知道如何进行。谁能给我解释一下?

注意:如果您想给出视觉解释,请同时提供文字解释,因为我看不到图像和图片。谢谢

【问题讨论】:

    标签: algorithm recursion sicp n-queens


    【解决方案1】:

    解决方案的措辞,或者更确切地说是算法的描述,可能有些复杂。

    基本上,它建议

    • 首先,在棋盘上创建所有个可能的八皇后组合,每列一个皇后。规定的步骤是:

      • 在第一行的第 1 列中放置一个皇后。然后将下一个皇后放在第一行的第 2 列。依此类推,到第 8 列。(显然,这个解决方案是无效的。)这是第一个位置(对于所有 8 个皇后)。
      • 接下来,完全相同,但对于第 8 列 (Q8) 中的皇后,将其放在第 2 行。这是第 2 号位置。
      • 接下来,Q8 在第 3 行。依此类推,直到 Q8 通过所有行。那是八个位置。
      • 接下来,Q1 到 Q6 保留第 1 行,Q7 移到第 2 行,Q8 从第 1 行开始。
      • 再次和之前一样,将 Q8 在第 1 行和第 8 行之间移动。另外 8 个位置。
      • Q1 到 Q6 仍然是第 1 行,Q7 到第 3 行,Q8 第 1 到 8 行。
      • 以此类推,直到 Q7 和 Q8 都到达第 8 行。
      • 现在轮到 Q6 移动到第 2 行。除此之外,再次重复 Q8 和 Q7 的模式。你可以看到递归。

      上面生成了 8^8 = 16777216 个解,很多个是无效的。这是“扩展位置集”。

    • 现在,在所有这些解决方案(“位置”)中,一个一个地检查它们,并保留那些没有一个皇后可以拿另一个的(你必须为每个位置逐步通过 7 个皇后,验证它们的行、列和对角线;然后自动计算 Q8)。

      如果您只保留有效位置,您将拥有所有八皇后拼图的有效配置。

    【讨论】:

    • 这不是解决方案描述的内容。它描述了全面移动,一次一列。在每一列,生成一个新位置并添加一个皇后,每行一个 - 然后在移动到下一列之前消除无效位置是“扩展集”。
    • @morbidCode 您能否接受我的回答,因为它不正确?
    【解决方案2】:

    Java 代码使用回溯,更改 SIZE 变量使其通用

    public class EightQueens {
    
        private final int SIZE = 10;
    
        private final int safe = 0;
        private final int cut = 1;
        private final int queen = 2;
    
        private int[][] mat = new int[SIZE][SIZE];
    
        private EightQueens() {
            for (int x = 0; x < SIZE; x++) {
                for (int y = 0; y < SIZE; y++) {
                    mat[x][y] = safe;
                }
            }
        }
    
        private void solveAll(){
            solve(mat);
        }
    
    
        private int[][] copy(int[][] mat){
            int[][] test = new int[SIZE][SIZE];
            for (int x = 0; x < SIZE; x++) {
                for (int y = 0; y < SIZE; y++) {
                    test[x][y] = mat[x][y];
                }
            }
            return test;
        }
    
        private void solve(int[][] matrix) {
    
           // System.out.println("solving ");
           // printMat(matrix);
    
            if (solved(matrix)) {
                printMat(matrix);
                return;
            }
            int unsafe;
            for (int x = 0; x < SIZE; x++) {
                unsafe = 0;
                for (int y = 0; y < SIZE; y++) {
                    if(matrix[x][y] == safe){
    
                        int[][] test = copy(matrix);
                        test[x][y] = queen;
                        //mark row
                        for(int z =0 ;z<SIZE;z++){
                            if(z == x){
                                continue;
                            }
                            test[z][y] = cut;
                        }
                        //mark col
                        for(int z =0 ;z<SIZE;z++){
                            if(z == y){
                                continue;
                            }
                            test[x][z] = cut;
                        }
    
                        //first diagonal
                        int a = x+1;
                        int b = y+1;
                        while(a<SIZE && b <  SIZE){
                            test[a][b] = cut;
                            a++;b++;
                        }
                        a = x-1;
                        b = y-1;
                        while(a>=0 && b >=0){
                            test[a][b] = cut;
                            a--;b--;
                        }
    
                        //second diagonal
                        a = x+1;
                        b = y-1;
                        while(a<SIZE && b >=  0){
                            test[a][b] = cut;
                            a++;b--;
                        }
                        a = x-1;
                        b = y+1;
                        while(a>=0 && b < SIZE){
                            test[a][b] = cut;
                            a--;b++;
                        }
    
                        solve(test);
                    }else {
                        unsafe++;
                        if(unsafe == SIZE){
                            return;
                        }
                    }
                }
            }
        }
    
        private void printMat(int[][] mat){
            for (int x = 0; x < SIZE; x++) {
                for (int y = 0; y < SIZE; y++) {
                    System.out.print(mat[x][y]+" ");
                }
                System.out.println();
            }
            System.out.println("_______________________________________________");
        }
    
        private boolean solved(int[][] mat) {
            int count = 0;
            for (int x = 0; x < SIZE; x++) {
                for (int y = 0; y < SIZE; y++) {
                    if (mat[x][y] == queen) {
                        count++;
                    }
                }
            }
            if (count == SIZE) {
                return true;
            } else {
                return false;
            }
        }
    
        public static void main(String[] args){
            EightQueens eightQueens = new EightQueens();
            eightQueens.solveAll();
        }
    
    }
    

    【讨论】:

      【解决方案3】:

      首先,使用列还是行都没有关系 - 输出将是相同的,因为问题是对称的。这种对称性会造成一些混乱;做好准备。

      不考虑您的具体问题,这里的想法是进行递归。这个问题谈到了 8 个皇后的安排。如果您放置了k-1 皇后,您将获得一个“位置”。从每个位置,你可以得到几个“扩展”位置,你在其中放置了一个更多的皇后(所以有k皇后)。所以对于每组带有k-1queens 的位置,都有一组带有kqueens 的位置。

      这个集合应该被“过滤” - 从中​​删除所有无效的位置。在某些情况下,它将是空的(无法放置另一个皇后);这绝不是一种特殊情况——它会发生很多。在其他情况下(实际上是大多数情况),它会很大。例如,对于“空”位置 - 没有皇后 - 将有几个(实际上是 8 个 - 见下文)放置 1 个皇后的“扩展位置”。

      现在,如何放置额外的女王并不重要。在一般情况下(放置任何棋子时),您应该将其放置在任何空闲方格上(并确保您确实检查了它们所有)。因为皇后像他们那样攻击,所以每一列中应该正好有一个皇后,所以只检查每个皇后的 8 个可能位置就足够了。例如,在“下一行”中。或者在“下一栏”中——它也可以。

      【讨论】:

        【解决方案4】:

        让我们看看我是否可以为您解决这个问题。我要把板子切成 5x5 以限制组合爆炸。

        您从一个空棋盘开始,放置了 0 个皇后。您现在想在第 1 列的每个合法位置放置一个皇后。您制作以下“便笺本”:

        1 0 0 0 0
        1 0 0 0 0
        1 0 0 0 0
        1 0 0 0 0
        1 0 0 0 0
        

        现在你顺着柱子往下走,消灭每一个非法放置的皇后……天哪,这很容易!这五个都是合法的。现在,您为每个合法展示位置生成一个位置。您从中获得五个职位:

        1 0 0 0 0    0 0 0 0 0    0 0 0 0 0    0 0 0 0 0    0 0 0 0 0
        0 0 0 0 0    1 0 0 0 0    0 0 0 0 0    0 0 0 0 0    0 0 0 0 0
        0 0 0 0 0    0 0 0 0 0    1 0 0 0 0    0 0 0 0 0    0 0 0 0 0
        0 0 0 0 0    0 0 0 0 0    0 0 0 0 0    1 0 0 0 0    0 0 0 0 0
        0 0 0 0 0    0 0 0 0 0    0 0 0 0 0    0 0 0 0 0    1 0 0 0 0
        

        我现在先把最后四个放在一边,继续看第一个。

        这是一个有一个皇后的位置,所以我们想在第二列中放置一个皇后。再次,我们制作一个便笺簿,在每一行放置一个皇后:

        1 1 0 0 0
        0 1 0 0 0
        0 1 0 0 0
        0 1 0 0 0
        0 1 0 0 0
        

        消除非法展示位置:

        1 0 0 0 0
        0 0 0 0 0
        0 1 0 0 0
        0 1 0 0 0
        0 1 0 0 0
        

        为每个合法展示位置生成一个新位置:

        1 0 0 0 0    1 0 0 0 0    1 0 0 0 0
        0 0 0 0 0    0 0 0 0 0    0 0 0 0 0
        0 1 0 0 0    0 0 0 0 0    0 0 0 0 0
        0 0 0 0 0    0 1 0 0 0    0 0 0 0 0
        0 0 0 0 0    0 0 0 0 0    0 1 0 0 0
        

        此时,如果您正在对解空间进行广度优先遍历,则将这些位置放在某种列表中以供以后处理,然后再执行第二个 1-queen 位置。如果您正在做深度优先(我建议节省一些内存),请将上面的第 2 和第 3 个位置与其余四个 1-queen 解决方案一起放入列表中,然后立即使用上面的第一个位置。

        在第 3 列的每一行放置一个皇后:

        1 0 1 0 0
        0 0 1 0 0
        0 1 1 0 0
        0 0 1 0 0
        0 0 1 0 0
        

        消除非法的:

        1 0 0 0 0
        0 0 0 0 0
        0 1 0 0 0
        0 0 0 0 0
        0 0 1 0 0
        

        并为每个合法展示位置生成一个新职位——而且只有一个。

        再继续两步,我们得到了第一个解决方案:

        1 0 0 0 0
        0 0 0 1 0
        0 1 0 0 0
        0 0 0 0 1
        0 0 1 0 0
        

        这被称为“运气”……我们经常在第一个分支上没有得到解决方案。例如,沿着这个位置向下第一个分支...

        1 0 0 0 0
        0 0 0 0 0
        0 0 0 0 0
        0 0 0 0 0
        0 1 0 0 0
        

        下一步给你

        1 0 0 0 0
        0 0 0 0 0
        0 0 1 0 0
        0 0 0 0 0
        0 1 0 0 0
        

        现在,第 4 列没有合法的位置。在这种情况下,您必须放弃并在等待名单上占据下一个合法位置。

        我强烈推荐递归、深度优先的解决方案。这使您可以在本地保留职位列表。在最深处,你只能有 8 次主动调用后加函数,每一次都被简单地限制在 8-k 个位置(不考虑对角线攻击);实际情况要少一些,因此您在所有级别上的活跃职位永远不会超过 25 个。在您完成将第二个皇后放置在所有分支上之前,广度优先将超过此值。

        【讨论】:

        • 第4个scartchpad 3的解数如何?
        • 您是指第 2 列暂存器的最终统计信息吗?在该列中放置皇后有三个合法位置;因此,要生成 3 个位置。
        • 我认为这让我感到困惑。我怎样才能找到合法的职位?或者如何知道女王是否合法?因为根据书本,如果在同一行、同一列或对角线上没有两个皇后,则两个皇后是合法的。但似乎第一个暂存器在每列第 1 行都有每个皇后。你说它们都是合法的,所以我认为发生了什么是每一列都是一个单独的位置?在那之后,我不知道你是如何做下一个职位的......
        • 我想我要问的是:如何找到三个合法职位?
        • 是的,暂存器 1 的第一列中有五个皇后。这产生了该层的五个位置,每个皇后一个。对于第二列(和第二个暂存器),我们一次只考虑一个位置。为了说明,我选择了左上角有女王的那个。在二级便签本上,第 1 列中只有一个皇后。" 在以后的迭代中,您将分别考虑其他位置。鉴于左上角的皇后,有三个合法的位置可以在第 2 列放一个皇后。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多