def solve(n):
#prepare a board
board = [[0 for x in range(n)] for x in range(n)]
#set initial positions
place_queen(board, 0, 0)
def place_queen(board, row, column):
"""place a queen that satisfies all the conditions"""
#base case
if row > len(board)-1:
print board
#check every column of the current row if its safe to place a queen
while column < len(board):
if is_safe(board, row, column):
#place a queen
board[row][column] = 1
#place the next queen with an updated board
return place_queen(board, row+1, 0)
else:
column += 1
#there is no column that satisfies the conditions. Backtrack
for c in range(len(board)):
if board[row-1][c] == 1:
#remove this queen
board[row-1][c] = 0
#go back to the previous row and start from the last unchecked column
return place_queen(board, row-1, c+1)
def is_safe(board, row, column):
""" if no other queens threaten a queen at (row, queen) return True """
queens = []
for r in range(len(board)):
for c in range(len(board)):
if board[r][c] == 1:
queen = (r,c)
queens.append(queen)
for queen in queens:
qr, qc = queen
#check if the pos is in the same column or row
if row == qr or column == qc:
return False
#check diagonals
if (row + column) == (qr+qc) or (column-row) == (qc-qr):
return False
return True
solve(4)
我为N-queen问题编写了Python代码,并且只要找到它就会打印出所有可能的解决方案。但是,我想修改此代码,以便它返回所有解决方案(板配置)的列表,而不是打印它们。我尝试修改代码如下:
def solve(n):
#prepare a board
board = [[0 for x in range(n)] for x in range(n)]
#set initial positions
solutions = []
place_queen(board, 0, 0, solutions)
def place_queen(board, row, column, solutions):
"""place a queen that satisfies all the conditions"""
#base case
if row > len(board)-1:
solutions.append(board)
return solutions
#check every column of the current row if its safe to place a queen
while column < len(board):
if is_safe(board, row, column):
#place a queen
board[row][column] = 1
#place the next queen with an updated board
return place_queen(board, row+1, 0, solutions)
else:
column += 1
#there is no column that satisfies the conditions. Backtrack
for c in range(len(board)):
if board[row-1][c] == 1:
#remove this queen
board[row-1][c] = 0
#go back to the previous row and start from the last unchecked column
return place_queen(board, row-1, c+1, solutions)
但是,只要找到第一个解决方案,就会返回,因此solutions
只包含一个可能的电路板配置。由于 print vs. return 让我对回溯算法感到困惑,如果有人能告诉我如何修改上述代码,以及将来如何解决类似问题,我将非常感激。
我假设使用全局变量会起作用,但我从某个地方学到了不鼓励使用全局变量来解决这个问题。有人可以解释一下吗?
编辑:
def solve(n):
#prepare a board
board = [[0 for x in range(n)] for x in range(n)]
#set initial positions
solutions = list()
place_queen(board, 0, 0, solutions)
return solutions
def place_queen(board, row, column, solutions):
"""place a queen that satisfies all the conditions"""
#base case
if row > len(board)-1:
#print board
solutions.append(deepcopy(board)) #Q1
#check every column of the current row if its safe to place a queen
while column < len(board):
if is_safe(board, row, column):
#place a queen
board[row][column] = 1
#place the next queen with an updated board
return place_queen(board, row+1, 0, solutions) #Q2
else:
column += 1
#there is no column that satisfies the conditions. Backtrack
for c in range(len(board)):
if board[row-1][c] == 1:
#remove this queen
board[row-1][c] = 0
#go back to the previous row and start from the last unchecked column
return place_queen(board, row-1, c+1, solutions) #Q3
以上内容返回所有找到的解决方案,而不是打印它们。我还有一些问题(相关部分在上面的代码中标记为Q1和Q2)
solutions.append(deepcopy(board))
?换句话说,当我们solutions.append(board)
时,究竟发生了什么?为什么这会导致附加[[0,0,0,0] ...]
的初始董事会?return place_queen(board, row+1, 0)
替换为place_queen(board, row+1, 0)
时,为什么会遇到问题?我们实际上并没有返回任何内容(或None
),但如果没有return
,则列表会超出索引。答案 0 :(得分:3)
使用Python的强大功能!我建议yield
找到您找到的解决方案,而不是return
。这样,您就可以为所有解决方案创建生成器。通过符号
[ solution for solution in solve(4) ]
或只是
list(solve(4))
编辑:
在您的情况下,solve()
和place_queen()
必须是生成器。在solve()
中你应该做最后一件事:return place_queen(board, 0, 0)
。通过这个你返回一个生成器。 (你也可以做for solution in place_queen(board, 0, 0): yield solution
,但这只会传递产生的值。)
在place_queen()
代替return place_queen(board, row+1, 0)
之类的回复,您应该执行for solution in place_queen(board, row+1, 0): yield solution
之类的内容。
EDIT2:
#!/usr/bin/env python
def solve(n):
#prepare a board
board = [[0 for x in range(n)] for x in range(n)]
#set initial positions
return place_queen(board, 0, 0)
def place_queen(board, row, column):
"""place a queen that satisfies all the conditions"""
#base case
if row > len(board)-1:
yield board
#check every column of the current row if its safe to place a queen
while column < len(board):
if is_safe(board, row, column):
#place a queen
board[row][column] = 1
#place the next queen with an updated board
for solution in place_queen(board, row+1, 0):
yield solution
return
else:
column += 1
#there is no column that satisfies the conditions. Backtrack
for c in range(len(board)):
if board[row-1][c] == 1:
#remove this queen
board[row-1][c] = 0
#go back to the previous row and start from the last unchecked column
for solution in place_queen(board, row-1, c+1):
yield solution
def is_safe(board, row, column):
""" if no other queens threaten a queen at (row, queen) return True """
queens = []
for r in range(len(board)):
for c in range(len(board)):
if board[r][c] == 1:
queen = (r,c)
queens.append(queen)
for queen in queens:
qr, qc = queen
#check if the pos is in the same column or row
if row == qr or column == qc:
return False
#check diagonals
if (row + column) == (qr+qc) or (column-row) == (qc-qr):
return False
return True
import sys, pprint
sys.setrecursionlimit(10000)
for solution in solve(8):
pprint.pprint(solution)
答案 1 :(得分:3)
在找到解决方案后,您需要调整代码以回溯,而不是返回。你只想在你发现自己回溯第一行时返回(因为这表明你已经探索了所有可能的解决方案)。
我认为如果您稍微更改代码的结构并无条件地在列上循环,这是最容易做到的,当您在行或列上超出界限时,回溯逻辑就会启动:
import copy
def place_queen(board, row, column, solutions):
"""place a queen that satisfies all the conditions"""
while True: # loop unconditionally
if len(board) in (row, column): # out of bounds, so we'll backtrack
if row == 0: # base case, can't backtrack, so return solutions
return solutions
elif row == len(board): # found a solution, so add it to our list
solutions.append(copy.deepcopy(board)) # copy, since we mutate board
for c in range(len(board)): # do the backtracking
if board[row-1][c] == 1:
#remove this queen
board[row-1][c] = 0
#go back to the previous row and start from the next column
return place_queen(board, row-1, c+1, solutions)
if is_safe(board, row, column):
#place a queen
board[row][column] = 1
#place the next queen with an updated board
return place_queen(board, row+1, 0, solutions)
column += 1
这适用于小型电路板(如4),但如果您尝试使用更大的电路板尺寸,则会达到Python的递归限制。 Python没有优化尾递归,因此这段代码在从一行转移到另一行时会构建大量的堆栈帧。
幸运的是,尾递归算法通常很容易变成迭代算法。以下是对上述代码的处理方法:
import copy
def place_queen_iterative(n):
board = [[0 for x in range(n)] for x in range(n)]
solutions = []
row = column = 0
while True: # loop unconditionally
if len(board) in (row, column):
if row == 0:
return solutions
elif row == len(board):
solutions.append(copy.deepcopy(board))
for c in range(len(board)):
if board[row-1][c] == 1:
board[row-1][c] = 0
row -= 1 # directly change row and column, rather than recursing
column = c+1
break # break out of the for loop (not the while)
elif is_safe(board, row, column): # need "elif" here
board[row][column] = 1
row += 1 # directly update row and column
column = 0
else: # need "else" here
column += 1 # directly increment column value
请注意,不需要使用不同的行和列起始值调用迭代版本,因此不需要那些作为参数。类似地,板和结果列表设置可以在开始循环之前完成,而不是在辅助函数中完成(进一步减少函数参数)。
稍微更多的Pythonic版本将是yield
结果的生成器,而不是在列表中收集它们以在最后返回,但这只需要一些小的更改(只需{{} 1}}而不是调用yield
)。使用生成器也可以让你在每次有解决方案时跳过复制板,只要你可以在再次推进生成器之前立即依赖发生器的消费者使用结果(或制作自己的副本)。
另一个想法是简化您的电路板表示。你知道在给定的行中只能有一个皇后,所以为什么不把列放在一个列表中的位置(带有一个标记值,如solutions.append
表示没有皇后已被安置)?所以1000
可以解决4皇后问题,皇后位于[1, 3, 0, 2]
,(0, 1)
,(1, 3)
和(2, 0)
(您可以使用这些元组(3, 2)
,如果你需要的话)。这样可以避免回溯步骤中的enumerate
循环,并且可能会检查方块是否也更安全,因为您不需要在棋盘上搜索皇后。
编辑以解决您的其他问题:
在第Q1点,您必须深度复制电路板,否则您最终会得到同一电路板的参考列表。比较:
for
对于Q2,您需要board = [0, 0]
results.append(board) # results[0] is a reference to the same object as board
board[0] = 1 # this mutates results[0][0] too!
result.append(board) # this appends another reference to board!
board[1] = 2 # this also appears in results[0][1] and result[1][1]
print(board) # as expected, this prints [1, 2]
print(results) # this however, prints [[1, 2], [1, 2]], not [[0, 0], [1, 0]]
来阻止代码进一步运行。如果要更清楚地表明返回值不重要,可以将return
语句与递归调用分开:
return
请注意,您当前的代码可能有效,但它正在做一些可疑的事情。您使用超出范围的place_queen(board, row+1, 0)
return
值(is_safe
)来调用row
,这只是因为您的row == len(board)
实施报告它们不安全(没有崩溃)您适当地回溯。当你在第0行的回溯代码中(在运行的最后),你只能正确退出,因为is_safe
循环在for
中找不到任何1
值(即最后一行)。我建议稍微重构一下你的代码,以避免依赖这些怪癖来正确操作。