Python中的N-queen回溯:如何返回解决方案而不是打印它们?

Python中的N-queen回溯:如何返回解决方案而不是打印它们?,python,recursion,backtracking,Python,Recursion,Backtracking,上面返回所有找到的解决方案,而不是打印它们。我还有几个问题(相关部分在上述代码中标记为Q1和Q2) 为什么我们需要解决方案。追加(deepcopy(board))?换句话说,当我们执行solutions.append(board)时,到底发生了什么?为什么这会导致追加初始board,即[[0,0,0,0]…] 当return place\u queen(board,row+1,0)被place\u queen(board,row+1,0)替换时,我们为什么会遇到问题?我们实际上没有返回任何内容(

上面返回所有找到的解决方案,而不是打印它们。我还有几个问题(相关部分在上述代码中标记为Q1和Q2)

  • 为什么我们需要
    解决方案。追加(deepcopy(board))
    ?换句话说,当我们执行
    solutions.append(board)
    时,到底发生了什么?为什么这会导致追加初始board,即
    [[0,0,0,0]…]
  • return place\u queen(board,row+1,0)
    place\u queen(board,row+1,0)
    替换时,我们为什么会遇到问题?我们实际上没有返回任何内容(或
    None
    ),但如果没有
    return
    ,列表将超出索引

  • 使用Python的强大功能!我建议
    给出
    您找到的解决方案,而不是
    返回
    。这样,您就为所有解决方案创建了一个生成器。用符号把它列出来是非常简单的(如果你真的需要的话)

    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
    
    或者干脆

    [ solution for solution in solve(4) ]
    
    编辑:

    在您的情况下,必须使
    solve()
    place\u queen()
    成为生成器。在
    solve()。这样你就可以返回一个发电机。(您也可以对就地解决方案(板,0,0)执行
    :生成解决方案
    ,但这只会传递生成的值。)

    place\u queen()
    中,而不是像
    return place\u queen(board,row+1,0)
    这样的返回操作,您应该像
    那样为解决方案执行就地(board,row+1,0):yield solution

    编辑2:

    list(solve(4))
    
    #/usr/bin/env python
    def解算(n):
    #准备一块木板
    board=[[0代表范围内的x(n)]代表范围内的x(n)]
    #设定初始位置
    返回位置(板,0,0)
    def place_queen(板、行、列):
    “放置一个满足所有条件的皇后”
    #基本情况
    如果行>列(板)-1:
    屈服板
    #检查当前行的每一列是否可以安全放置皇后
    列<列(板):
    如果是安全的(板、行、列):
    #立王后
    线路板[行][列]=1
    #使用更新的板放置下一个皇后
    对于就地解决方案(电路板,第+1行,第0行):
    屈服解
    返回
    其他:
    列+=1
    #没有满足条件的列。回溯
    对于范围内的c(透镜(板)):
    如果电路板[row-1][c]==1:
    #除掉这个女王
    董事会[第1行][c]=0
    #返回上一行,从最后一个未选中的列开始
    对于就地解决方案(电路板,第1排,c+1):
    屈服解
    def是安全的(板、行、列):
    “”“如果没有其他女王在(第二排,女王)威胁女王,则返回True”“”
    皇后区=[]
    对于范围内的r(透镜(板)):
    对于范围内的c(透镜(板)):
    如果电路板[r][c]==1:
    皇后=(r,c)
    皇后。附加(皇后)
    皇后区皇后区:
    qr,qc=女王
    #检查pos是否在同一列或同一行中
    如果行==qr或列==qc:
    返回错误
    #检查对角线
    如果(行+列)=(qr+qc)或(列行)=(qc qr):
    返回错误
    返回真值
    导入系统,pprint
    系统设置递归限制(10000)
    对于solve(8)中的解决方案:
    pprint.pprint(解决方案)
    
    在找到解决方案后,您需要调整代码以回溯,而不是返回。您只想在发现自己从第一行返回时返回(因为这表明您已经探索了所有可能的解决方案)

    我认为这是最容易做到的,如果您稍微更改代码的结构,并在列上无条件地循环,当您在行或列上超出边界时,回溯逻辑就会启动:

    #!/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)
    
    这将适用于小型电路板(如4),但如果您尝试在较大的电路板上使用它,将达到Python的递归限制。Python不优化尾递归,因此当代码从一行移到另一行时,它会构建大量堆栈帧

    幸运的是,尾部递归算法通常很容易转化为迭代算法。以下是如何对上述代码执行此操作:

    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
    
    请注意,不需要使用不同的行和列起始值来调用迭代版本,因此这些值不需要作为参数。类似地,电路板和结果列表设置可以在开始循环之前完成,而不是在助手函数中完成(进一步减少函数参数)

    一个稍微有点python风格的版本是一个生成器,
    产生它的结果,而不是将它们收集到一个列表中,然后在最后返回,但这只需要一些小的更改(只需
    产生
    ,而不是调用
    解决方案。追加
    )。使用生成器还可以让您在每次有解决方案时跳过复制电路板,只要您可以依靠生成器的使用者在再次推进生成器之前立即使用结果(或制作自己的副本)

    另一个想法是简化董事会的代表性。您知道在给定的行中只能有一个皇后,所以为什么不将该列存储在一维列表中(使用哨兵值,如
    1000
    指示未放置皇后)?所以
    [1,3,0,2]
    将是4皇后问题的解决方案,皇后位于
    (0,1)
    (1,3)
    (2,0)
    (3,2)
    (如果需要,您可以使用
    枚举
    获得这些元组)。这样可以避免回溯步骤中的
    for
    循环,并且可能也更容易检查正方形是否安全,因为您不需要在棋盘上搜索皇后

    编辑以解决其他问题:

    #!/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)
    
    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
    
    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
    
    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]]
    
    place_queen(board, row+1, 0)
    return