用python实现N皇后问题

一、N皇后I

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

用python实现N皇后问题_第1张图片

上图为 8 皇后问题的一种解法。

给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

示例:

输入: 4

输出: [

[".Q…", // 解法 1

“…Q”,

“Q…”,

“…Q.”],

["…Q.", // 解法 2

“Q…”,

“…Q”,

“.Q…”]

]

解释: 4 皇后问题存在两个不同的解法。

算法设计:

  • 在n元组x[1:n]表示n后问题的解。其中,x[i]表示皇后i放在棋盘的第i行的第x[i]列。由于不允许将2个皇后放在同一列上,所有解向量中的x[i]互不相同。2个皇后不能放在同一斜线上是问题的隐约束。对于一般的n后问题,这一隐约束条件可以化成显约束的形式。如果将nn格的棋盘看做二维方阵,其行号从上到下,列号从左至右一次编号1,2,……,n,从棋盘左上角到右下角的主对角线及其平行线(即斜率为-1的各斜线)上,2个下标值的差(行号-列号)值相等。同理,斜率为+1的每一条斜线上,2个下标值的和(行号+列号)值相等……

回溯算法:

  • 记录行,列, 正对角,负对角,不能有两个以上的棋子.
    如何判断是否在对角上呢?
    正对角就是相加之和一样的
    负对角就是相减只差一样的

72 ms

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        res = []
        s = "." * n
        def backtrack(i, tmp,col,z_diagonal,f_diagonal):
            if i == n:
                res.append(tmp)
                return 
            for j in range(n):
                if j not in col and i + j not in  z_diagonal and i - j not in f_diagonal:
                    backtrack(i+1,tmp + [s[:j] + "Q" + s[j+1:]], col | {j}, z_diagonal |{i + j} , f_diagonal |{i - j}  ) 
            
        backtrack(0,[],set(),set(),set())    
        return res

64 ms

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        def traceBack(r):
            #得到一个合理结果
            if r >= n:
                temp = []
                for i in matrix:
                    temp.append(''.join(i))
                result.append(temp)
                return
            #只需对此行的各列进行分析
            for i in range(n):
                if col[i] and hill_dig[r + i] and dale_dig[r - i + n - 1]:
                    matrix[r][i] = 'Q'
                    #束缚条件
                    col[i] = False
                    hill_dig[r+i] = False
                    dale_dig[r-i+n-1] = False
                    #对下一行进行分析
                    traceBack(r + 1)
                    #回退,束缚条件重置
                    matrix[r][i] = '.'
                    col[i] = True
                    hill_dig[r + i] = True
                    dale_dig[r - i + n - 1] = True
            return

        result = []
        matrix = [['.' for i in range(n)] for i in range(n)]
        col = [True for i in range(n)]
        dale_dig = [True for i in range(2 * n - 1)] #主对角线 row-col常数
        hill_dig = [True for i in range(2 * n - 1)] #从对角线,row+col常数
        traceBack(0)
        return result

60 ms

class Solution(object):
    def solveNQueens(self, n):
        def DFS(queens, xy_dif, xy_sum):
            p = len(queens)
            if p == n:
                result.append(queens)
                return None
            for q in range(n):
                if q not in queens and p-q not in xy_dif and p+q not in xy_sum:
                    DFS(queens+[q], xy_dif+[p-q], xy_sum+[p+q])
        
        result = []
        DFS([], [], [])
        return [ ["."*i + "Q" + "."*(n-i-1) for i in sol] for sol in result ]

二、N皇后 II

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

用python实现N皇后问题_第2张图片

上图为 8 皇后问题的一种解法。

给定一个整数 n,返回 n 皇后不同的解决方案的数量。

示例:

输入: 4

输出: 2

解释: 4 皇后问题存在如下两个不同的解法。

[

[".Q…", // 解法 1

“…Q”,

“Q…”,

“…Q.”],

["…Q.", // 解法 2

“Q…”,

“…Q”,

“.Q…”]

]

算法设计:

  • 在n元组x[1:n]表示n后问题的解。其中,x[i]表示皇后i放在棋盘的第i行的第x[i]列。由于不允许将2个皇后放在同一列上,所有解向量中的x[i]互不相同。2个皇后不能放在同一斜线上是问题的隐约束。对于一般的n后问题,这一隐约束条件可以化成显约束的形式。如果将nn格的棋盘看做二维方阵,其行号从上到下,列号从左至右一次编号1,2,……,n,从棋盘左上角到右下角的主对角线及其平行线(即斜率为-1的各斜线)上,2个下标值的差(行号-列号)值相等。同理,斜率为+1的每一条斜线上,2个下标值的和(行号+列号)值相等……

回溯算法

  • 记录 行, 列, 正对角,负对角,不能有两个以上的棋子.
    如何判断是否在对角上呢?
    正对角就是相加之和一样的
    负对角就是相减只差一样的

56 ms

class Solution:
    def totalNQueens(self, n: int) -> int:
        self.res = 0
        def backtrack(i,col,z_diagonal,f_diagonal):
            if i == n:return  True
            for j in range(n):
                if j not in col and i + j not in  z_diagonal and i - j not in f_diagonal:
                    if backtrack(i+1, col | {j}, z_diagonal |{i + j} , f_diagonal |{i - j}  ) :
                        self.res += 1
            return False
        backtrack(0,set(),set(),set())    
        return self.res

52 ms

class Solution:
    def totalNQueens(self, n: int) -> int:
        def DFS(n: int, row: int, cols: int, left: int, right: int):
            """ 深度优先搜索
            :param n: N皇后个数
            :param row: 递归的深度
            :param cols: 可被攻击的列
            :param left: 左侧斜线上可被攻击的列
            :param right: 右侧斜线上可被攻击的列
            """
            if row >= n:
                self.res += 1
                return

            # 获取当前可用的空间
            bits = (~(cols | left | right)) & ((1 << n) - 1)

            # 遍历可用空间
            while bits:
                # 获取一个位置
                p = bits & -bits
                DFS(n, row + 1, cols | p, (left | p) << 1, (right | p) >> 1)
                bits = bits & (bits - 1)

        if not (n == 1 or n >= 4):
            # N皇后问题只有在 N 大于等于 4 或等于 1 的时候才有解
            return 0
        self.res = 0
        DFS(n, 0, 0, 0, 0)
        return self.res

使用 bitmap 回溯

44 ms

class Solution:
    def totalNQueens(self, n):
        """
        :type n: int
        :rtype: int
        """
        def backtrack(row = 0, hills = 0, next_row = 0, dales = 0, count = 0):
            """
            :type row: 当前放置皇后的行号
            :type hills: 主对角线占据情况 [1 = 被占据,0 = 未被占据]
            :type next_row: 下一行被占据的情况 [1 = 被占据,0 = 未被占据]
            :type dales: 次对角线占据情况 [1 = 被占据,0 = 未被占据]
            :rtype: 所有可行解的个数
            """
            if row == n:  # 如果已经放置了 n 个皇后
                count += 1  # 累加可行解
            else:
                # 当前行可用的列
                # ! 表示 0 和 1 的含义对于变量 hills, next_row and dales的含义是相反的
                # [1 = 未被占据,0 = 被占据]
                free_columns = columns & ~(hills | next_row | dales)
                
                # 找到可以放置下一个皇后的列
                while free_columns:
                    # free_columns 的第一个为 '1' 的位
                    # 在该列我们放置当前皇后
                    curr_column = - free_columns & free_columns
                    
                    # 放置皇后
                    # 并且排除对应的列
                    free_columns ^= curr_column
                    
                    count = backtrack(row + 1, 
                                      (hills | curr_column) << 1, 
                                      next_row | curr_column, 
                                      (dales | curr_column) >> 1, 
                                      count)
            return count

        # 棋盘所有的列都可放置,
        # 即,按位表示为 n 个 '1'
        # bin(cols) = 0b1111 (n = 4), bin(cols) = 0b111 (n = 3)
        # [1 = 可放置]
        columns = (1 << n) - 1
        return backtrack()

你可能感兴趣的:(数据结构)