【问题标题】:N Queens: backtracking solution implemented by Python generatorN Queens:Python生成器实现的回溯解决方案
【发布时间】:2014-09-23 00:59:44
【问题描述】:

这个生成器是如何工作的?它显然在外部 for 循环期间发生了变化。生成器是否在 for 循环期间进行评估?

代码改编自http://rosettacode.org/wiki/N-queens_problem#Python
如果我导入代码,它会显示:
[[1, 3, 0, 2], [2, 0, 3, 1]]

在代码之前它说:
“对上述代码的一个令人惊讶的简单更改(将列表理解更改为生成器表达式)会产生回溯解决方案:”

class Solution:
    # @return a list of lists of string
    def under_attack(self, col, queens):
        return col in queens or any(abs(col - x) == len(queens)-i for i,x in enumerate(queens))

    def solve(self,n):
        solutions = [[]]
        for row in range(n):
            solutions = (solution+[i] for solution in solutions
                                      for i in range(n)    
                                      if not self.under_attack(i, solution))
        print(list(solutions))
        return solutions


A=Solution()
list(A.solve(4))

【问题讨论】:

  • 请重新格式化您的代码,冒号在两处丢失,右括号也丢失。我的猜测是 solutions 在某些生成器(?)的生命周期内发生变化,但我不太理解你的代码......
  • 在哪里?我猜你认为丢失的地方只是断线。求解函数中只有一个外部 for 循环。
  • 我看到它被编辑了,谢谢。

标签: python generator n-queens


【解决方案1】:

该算法的工作方式与使用列表推导式的算法完全相同,唯一的区别在于它使用了generator 表达式——事实上,它创建了一个生成器其中嵌套了更多生成器嗯>!因此,它不是一次列出所有可能的解决方案,而是根据需要懒惰地生成更多解决方案。

您可以通过在每次调用 under_attack 时增加一些全局定义的计数器变量来轻松验证这一点,并查看该计数器增加了多少次。

gen = solve(8) # just create the generators, under_attack called 0 times
next(gen)      # first solution,             under_attack called 876 times
list(gen)      # all remaining solutions,    under_attack called 15720

如果您执行list(gen),它会生成所有解决方案,而next(gen) 只计算一个。


现在是实际的算法。这在非生成器版本中更容易解释。另外,不需要上课。

def under_attack(col, queens):
    return col in queens or any(abs(col - x) == len(queens)-i         # (1)
                                for i,x in enumerate(queens))         # (2)

def solve(n):
    solutions = [[]]
    for row in range(n):                                              # (3)
        solutions = [solution+[i] for solution in solutions           # (4)
                                  for i in range(n)                   # (5)
                                  if not under_attack(i, solution)]   # (6)
        print solutions # remove this when using generator!           # (7)
    return solutions

首先,under_attack 函数。给定一个皇后的列和已经放置的皇后的列,这将检查同一列中是否已经有一个皇后(1,在or之前),或者皇后的any是否在同一列对角线 (2)。

现在是solve:这会遍历棋盘的所有行,每行放置一个皇后。 solutions 是部分解决方案的列表,其中每个子列表包含到目前为止放置的皇后的列。 [[3,1]] 意味着一个(部分)解决方案在第 0 行第 3 列和第 1 行第 1 列有一个女王。现在,对于每一行 (3),它会使用部分的每个组合更新部分解决方案到目前为止的行 (4) 和新女王 (5) 的行的解决方案,其中女王不会受到攻击 (6)。

我选择非生成器版本来解释的原因是这样我们可以在循环的每次迭代中打印部分解决方案 (7)。使用生成器这是不可能的,因为仅打印列表会耗尽生成器,并且没有更多的部分解决方案可以构建。对于n=4

[[0],            [1],          [2],         [3]]               # 1st queen 
[[0, 2], [0, 3], [1, 3],       [2, 0],      [3, 0], [3, 1]]    # 2nd queen 
[[0, 3, 1],      [1, 3, 0],    [2, 0, 3],   [3, 0, 2]]         # 3rd queen 
[                [1, 3, 0, 2], [2, 0, 3, 1]]                   # 4th queen 

第一个皇后可以放在任何一列。这已经限制了第二个皇后的可能位置,现在只有一两个可能的位置。第三个皇后更受限制,对于皇后1和2的某些位置,根本找不到位置。最后放最后一个皇后,这就是解集。

【讨论】:

  • 谢谢。我现在知道了。我想令人困惑的部分是代码一直使用可变解决方案,但实际上它为每一行构建了一个生成器并将它们嵌套。实际上列表理解版本也是回溯。那么原始解释是否具有误导性? (“对上述代码的一个令人惊讶的简单更改(将列表理解更改为生成器表达式)会产生回溯解决方案:”)
  • @user3262130 嗯,好问题...通常我会将“回溯”与递归函数或使用堆栈的函数相关联,实际上解决方案是 off堆栈(实际堆栈或递归堆栈)。我认为,这个可以被视为一种回溯算法,因为它会在不可行的解决方案上提前停止,但话又说回来,列表理解也是如此......而且,可以使用生成器解决方案只创建一个解决方案然后停止,这也是回溯的常见(但恕我直言未定义)特征。
猜你喜欢
  • 1970-01-01
  • 2021-04-15
  • 2013-03-29
  • 1970-01-01
  • 1970-01-01
  • 2013-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多