为啥我的 N Queens 算法会到达最后一行?

Posted

技术标签:

【中文标题】为啥我的 N Queens 算法会到达最后一行?【英文标题】:Why is my N Queens algorithm reaching the last row?为什么我的 N Queens 算法会到达最后一行? 【发布时间】:2020-04-08 18:32:59 【问题描述】:

我想我理解 NQueens 算法的要点 - 你检查每一行是否有可用空间,如果它们存在,则在其中放置一个皇后,然后递归调用该算法到下一行。这是我的算法(N 大小为 3)

from typing import List
import pdb
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        self.solutions = 0 
        self.attack_zone = [[0]*n for i in range(n)]
        self.size = n
        self.backtrack_nqueens(0,0) #start on row 0 
        return self.solutions
    def backtrack_nqueens(self,row,iteration):
        #starting at row
        for col in range(self.size):
            if self.is_not_under_attack(row,col):
                print("on iteration",iteration,row,col,"was a safe place for some reason")
                #adds queen to this row
                self.place_or_remove_queen(row,col,True)
                if row + 1 == self.size:
                    ## THIS SHOULD NEVER HAPPEN 
                    self.solutions += 1
                else:
                    self.backtrack_nqueens(row+1,iteration+1)
                #removes queen from this row
                self.place_or_remove_queen(row,col,False)

    def place_or_remove_queen(self,row,col,place):
        flag = 1 if place else 0 
        self.attack_zone[row] = [flag]*self.size
        #for col
        for r in range(self.size):
            self.attack_zone[r][col] = flag
        #lower right
        for r,c in zip(range(row,self.size,1),range(col,self.size,1)):
            self.attack_zone[r][c] = flag
        #upper right
        for r,c in zip(range(row,-1,-1),range(col,self.size,1)):
            self.attack_zone[r][c] = flag
        #lower left
        for r,c in zip(range(row,self.size,1),range(col,-1,-1)):
            self.attack_zone[r][c] = flag
        #upper left
        for r,c in zip(range(row,-1,-1),range(col,-1,-1)):
            self.attack_zone[r][c] = flag

    def is_not_under_attack(self,row,col):
        return self.attack_zone[row][col] == 0
s = Solution()
print(s.solveNQueens(3))

当我运行它时,self.solutions 以 3 结束 - 基本上它为女王找到 3 个位置,我们都知道这是不应该发生的。我不明白它是如何到达我说##这永远不会发生的那一行的。

我唯一能想到的是,我以某种方式移除了一个不应该被移除的女王?所以我的attack_zone在不应该的时候有空格。有人可以指出我在递归中做错了什么会导致这种情况,为什么?

【问题讨论】:

【参考方案1】:

问题在于,当您移除一个皇后时,您将相应的方格标记为“空闲”,即将它们的“威胁计数”设置为零,而不管其他皇后是否也威胁该方格。考虑到您的示例,会发生以下情况:

# Iteration 1     # Iteration 2     # Iteration 3

| 1 | 1 | Q |     | 1 | 1 | Q |     | 0 | 0 | Q |
+---+---+---+     +---+---+---+     +---+---+---+
| 0 | 1 | 1 |     | Q | 1 | 1 |     | 0 | 0 | 0 |
+---+---+---+     +---+---+---+     +---+---+---+
| 1 | 0 | 1 |     | 1 | 1 | 1 |     | 0 | 0 | 1 |

在第 3 次迭代中,(1, 0) 上的皇后被移除,所有对应的方格都被标记为0,这也是(0, 2) 上的皇后实际威胁的方格。 (1, 1)(1, 2) 上的皇后也会发生同样的情况,后者最终会在最后一行 (2, 0) 上形成一个空闲方块。

为了使算法正常工作,您可以维护一个“威胁计数”,该计数跟踪一个方格受到威胁的皇后数量。放置一个女王会增加一个计数,移除一个女王会减少一个。您可以通过将flag 的值更改为flag = 1 if place else -1 来实现这一点,然后在任何地方使用self.attack_zone[r][c] += flag 而不是self.attack_zone[r][c] = flag。这给出了以下图片:

# Iteration 1     # Iteration 2     # Iteration 3

| 1 | 1 | Q |     | 2 | 2 | Q |     | 1 | 1 | Q |
+---+---+---+     +---+---+---+     +---+---+---+
| 0 | 1 | 1 |     | Q | 2 | 2 |     | 0 | 1 | 1 |
+---+---+---+     +---+---+---+     +---+---+---+
| 1 | 0 | 1 |     | 2 | 1 | 1 |     | 1 | 0 | 1 |

【讨论】:

以上是关于为啥我的 N Queens 算法会到达最后一行?的主要内容,如果未能解决你的问题,请参考以下文章

leetcode第一刷_N-Queens

51/52. N-Queens -- 待优化

算法:N皇后问题51. N-Queens

算法:N皇后问题51. N-Queens

为啥我的递归会产生意外错误?以及如何在到达循环时修改递归?

程序员算法基础——贪心算法