zoukankan      html  css  js  c++  java
  • N皇后问题

    问题来源

    八皇后问题(英文:Eight queens),是由国际西洋棋棋手马克斯·贝瑟尔于1848年提出的问题,是回溯算法的典型案例。

    问题表述为:在8X8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

    高斯(高等数学男神)认为有76种方案,后来有人用图论的方法解出92种结果。

    八皇后问题

    N皇后问题

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

    解题思路

    规则解读

    规则:任意两个皇后都不能处于同一行、同一列或同一斜线上

    对于行列的判断,比较直观,只需检查对应皇后的行列号即可判断是否有两个以上的皇后处于同一行或同一列,而对于判断是否在同一斜线上,则需要找规律,这里参照力扣官方的解题思路:

    • 对于所有的“上坡”对角线:行号+列号=常数

    • 对于所有的“下坡”对角线:行号-列号=常数

    如何定义皇后的位置

    可能大多数人的想法是定义每一个皇后的横纵坐标,但是在实际指定皇后位置的时候,我们是挨个指定的,也就是说,我们不可能同时指定多个皇后的位置,因为略过了判定步骤,会造成位置重复。

    因此,我们可用利用这个指定顺序,作为皇后的一个维度的坐标,这样一来,指定皇后位置时,只需要指定一个坐标即可,实际上每个皇后依然有横纵坐标,只不过其中的一个坐标维度被隐藏了。

    需要使用的方法

    很明显的,这里需要用到递归算法,因为即使不使用暴力穷举,当N增大到一定程度时,所需要做的运算量依然是惊人的。

    除此之外,还需要使用到的一个非常重要的核心方法:回溯算法。在指定某一个皇后位置时,如果发现该维度(行/列)的所有位置均得不到结果,则需要重新确定当前维度的位置,也就是说前面指定的皇后位置无法得到结果,需要回退,这里的回退即是回溯算法的精髓,即回退只是回退一步,而不是之前的所有工作全部销毁

    百度百科定义:回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

    8皇后代码(力扣官方)

    class Solution:
        def solveNQueens(self, n):
            def could_place(row, col):
                # 如果列,‘上坡对角线’位置值,‘下坡对角线’位置值,全部为0,则意味着该位置未放置皇后,可以放置,否则返回False
                return not (cols[col] + hill_diagonals[row - col] + dale_diagonals[row + col])
    
            def place_queen(row, col):
                queens.add((row, col))
                # 放置皇后后,该位置列位置值,‘上坡对角线’位置值,‘下坡对角线’位置值全部置为1
                cols[col] = 1
                # 对角线可能经过多个位置,但只要该对角线的一个位置被放置皇后,则整条对角线经过的位置均不可再放置皇后
                hill_diagonals[row - col] = 1
                dale_diagonals[row + col] = 1
    
            def remove_queen(row, col):
                queens.remove((row, col))
                # 清除皇后后,该位置列位置值,‘上坡对角线’位置值,‘下坡对角线’位置值全部置为0
                cols[col] = 0
                hill_diagonals[row - col] = 0
                dale_diagonals[row + col] = 0
    
            def add_solution():
                solution = []
                # 进行排序,对于元素是元组的集合而言,会按照元组的第一个元素排序,第一个元素相同使用第二个,以此类推,排序完成,
                # 会将集合转为列表,由于这里皇后位置的判定使用的是列位置,行是递增的,即逐行判断每一列,所以排序后,即是1-n行
                for _, col in sorted(queens):
                    solution.append('*     ' * col + 'Q     ' + '*     ' * (n - col - 1))
                output.append(solution)
    
            # 此回溯函数可进可退
            def backtrack(row=0):
                # 遍历n次,相当于在第一行的各个列位置确定的条件下,有多少种解法,查看打印结果找规律
                # 每次递归,行都是递增的,
                for col in range(n):
                    if could_place(row, col):
                        place_queen(row, col)
                        # 判断是否完成一次成功放置
                        if row + 1 == n:
                            add_solution()
                        else:
                            # 递归
                            backtrack(row + 1)
                        # 回退一步
                        # 何时回退?正确时回退,错误时也会回退
                        # 确定一个位置结果后,回退一步,查看最后一步是否还有其他结果,从后向前递归
                        # 比如,确定8行的列位置(即确定第八个皇后的位置),确定为2列,记录结果后,回退一步,列序号+1,看一下8行的3-8列是否也可以
                        remove_queen(row, col)
    
            # 初始化,棋盘每个位置均可放置皇后,位置值均为0
            cols = [0] * n
            hill_diagonals = [0] * (2 * n - 1)
            dale_diagonals = [0] * (2 * n - 1)
            queens = set()
            output = []
            backtrack()
            return output
    
    
    if __name__ == '__main__':
        eightQueen = Solution()
        # 输出结果并打印至文本文件
        for i in range(len(eightQueen.solveNQueens(8))):
            with open(r'C:\Users\86188\Desktop\Python_Study\homework\.NQueen.txt', 'a') as fp:
                print(f'第{i + 1}个解', file=fp)
                for j in range(len(eightQueen.solveNQueens(8)[i])):
                    print(eightQueen.solveNQueens(8)[i][j], '\n', file=fp)
                print('\n', file=fp)
                fp.flush()
    

    运行结果(前8个,供结果分析)

    部分运行结果
  • 相关阅读:
    在阿里写了8年代码后,我才明白这些道理
    2017双11交易系统TMF2.0技术揭秘,实现全链路管理
    加入新公司快速进入状态的心得
    Kibana+ElasticSearch实现索引数据的几种查询方式
    记一次jenkins发生的无法判断字符串前后空格
    ansible-playbook调试
    记一次ansible-playbook jenkins传空格的标量导致删除了服务的主目录
    rabbitmq集群中队列的完整性
    html5分割上传实现超大文件无插件网页上传思路
    html5分割上传实现超大文件无插件网页上传功能
  • 原文地址:https://www.cnblogs.com/lotuslaw/p/13575057.html
Copyright © 2011-2022 走看看