一.题目需求

n皇后问题是一道比较经典的算法题。它研究的是将n个皇后放置在一个n×n的棋盘上,使皇后彼此之间不相互攻击。

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

二.算法思想

1.构建棋盘

可以用一个n×n列表来表示棋盘,设皇后所在的位置为board[i],i代表行,board[i]代表列,因此皇后所处的位置就是第i行、第board [i]列。

如下,第一个皇后就处于[0,0]位置(以0为起点,[0,0]意为第一行第一列),第二个皇后就处于[2,3]位置(意为第三行第四列):

2.不攻击检查

即需要判断:
1)是否处于同一列中
2)是否在左斜线上:(行 + 列)的值不可相等
3)是否在右斜线上:(列 - 行)的值不可相等
这里,每行肯定只有1个皇后,是很显然的,因此不必特别判断,
左右斜线的判断可以用一个绝对值公式abs(board[i] - col) == abs(i - row)判断,这样就不需要写两个公式。

    # 校验是否有效
def is_valid(board, row, col):
for i in range(row):
if board[i] == col or abs(board[i] - col) == abs(i - row):
return False
return True

3.DFS搜索,回溯算法
1)结束条件:当前行数 = 皇后总数,即最后一行已经成功放入皇后
2)循环一行中的每一个位置,若不发生攻击事件,可将皇后放入该位置
3)继续下一行的搜索,即传入的参数为当前行数 + 1

    # DFS搜索,回溯算法
def backtrack(board, row):
# 探索行号等于n时结束
if row == n:
result.append(board[:])
return
# 根据当前行号,再遍历每一列位置
for col in range(n):
# 检测当前行号,列号是否有效
if is_valid(board, row, col):
# 有效则设置该位置为皇后
board[row] = col
# 探索下一行,每次探索一行,放置1个皇后
backtrack(board, row + 1)

4.算法分析

这个算法的时间复杂度是O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。

三.编程实现

根据网上搜集学到的实现代码,多数都采用一维数组方式实现,每次探索每行的每一列,代码更简洁。

实现方法一:

class SolutionNQueens(object):
'''
回溯算法-一维数组解决N皇后问题。
该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
''' def __init__(self, num):
self.count = 0
self.num = num # 校验当前行号,列号是否有效
def is_valid(self, board, row, col):
# 遍历行号
for i in range(row):
if board[i] == col or abs(board[i] - col) == abs(i - row):
return False
return True def backtrack(self, board, row, result):
if row == self.num:
result.append(board[:])
self.count += 1
return
for col in range(self.num):
if self.is_valid(board, row, col):
board[row] = col
self.backtrack(board, row + 1, result)
board[row] = 0 def backtrack_result(self):
result = []
# 最终皇后的位置 (下标:第几行 数值:第几列)
board = [0] * self.num
# 从第一行开始
row = 0
self.backtrack(board, row, result)
return result

同样采用一维数组方式实现,优化减少部分无效列号的遍历,每次探索部分列即可,耗时减少很多。

实现方法二:

class SolutionNQueensNew(object):
'''
回溯算法-一维数组解决N皇后问题,优化减少部分无效列号的遍历。
该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
''' def __init__(self, num):
self.count = 0
self.num = num # 校验当前行号,列号是否有效
def is_valid(self, board, row, col):
# 遍历行号
for i in range(row):
if board[i] == col or abs(board[i] - col) == abs(i - row):
return False
return True def backtrack(self, board, row, range_col, result):
if row == self.num:
result.append(board[:])
self.count += 1
return
# 根据当前行号,再遍历列号表中的列号
for col in range_col:
if self.is_valid(board, row, col):
# 有效则设置该位为 皇后
board[row] = col
# 列号表中删除该皇后位的列号,减少无效遍历次数
range_col.remove(col)
# 探索下一行,每次探索一行,放置1个皇后
self.backtrack(board, row + 1, range_col, result)
# 探索失败,回溯,还原该位置为 0-空位
board[row] = 0
# 还原列号表,列表尾部添加元素
range_col.append(col)
# sort 增序排序
range_col.sort() def backtrack_result(self):
result = []
# 最终皇后的位置 (下标:第几行 数值:第几列)
board = [0] * self.num
# 从第一行开始
row = 0
# 列号表初始化,每一列都探索
range_col = [i for i in range(self.num)]
self.backtrack(board, row, range_col, result)
return result

采用二维数组方式实现,每次探索每行每列,代码稍微复杂点,检测是否有效方法也不同。

实现方法三:

def solve_n_queens(n):
'''
回溯算法-二维数组解决N皇后问题
该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
''' def is_valid(board, row, col):
'''
board(一个二维列表,表示棋盘),
row(一个整数,表示要检查的行索引),
col(一个整数,表示要检查的列索引)。
函数的目的是检查在给定的行和列上放置一个皇后是否有效。
''' '''
函数首先遍历当前行之前的所有行,检查是否有任何皇后在同一列上。
如果有,函数返回False,表示放置皇后无效。
'''
for i in range(row):
if board[i][col] == 1:
return False
'''
zip循环检查左上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
'''
for i, j in zip(range(row - 1, -1, -1), range(col - 1, -1, -1)):
if board[i][j] == 1:
return False
'''
zip循环检查右上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
'''
for i, j in zip(range(row - 1, -1, -1), range(col + 1, n)):
if board[i][j] == 1:
return False
return True def backtrack(board, row):
# 探索行号等于N时结束
if row == n:
# 将棋盘可行方案数据添加到结果列表中
result.append([[board[i][j] for j in range(n)] for i in range(n)])return
# 根据当前行号,再遍历列号
for col in range(n):# 检测当前行号,列号是否有效
if is_valid(board, row, col):
# 有效则设置该方格为 1-皇后
board[row][col] = 1
# 探索下一行,每次探索一行,放置1个皇后
backtrack(board, row + 1)
# 探索失败,回溯,还原该方格为 0-空位
board[row][col] = 0 # 返回结果列表
result = []# 创建n×n的棋盘,2维数组,其中1表示皇后,0表示空格
board = [[0] * n for _ in range(n)]
# 回溯算法,从第1行开始探索
backtrack(board, 0)
return result

采用二维数组方式实现,优化减少部分无效列号的遍历,每次探索部分列即可,耗时减少很多。

实现方法四:

def solve_n_queens_new(n):
'''
回溯算法-二维数组解决N皇后问题,优化减少部分无效列号的遍历。
该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
''' def is_valid(board, row, col):
'''
board(一个二维列表,表示棋盘),
row(一个整数,表示要检查的行索引),
col(一个整数,表示要检查的列索引)。
函数的目的是检查在给定的行和列上放置一个皇后是否有效。
''' '''
zip循环检查左上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
'''
for i, j in zip(range(row - 1, -1, -1), range(col - 1, -1, -1)):
if board[i][j] == 1:
return False
'''
zip循环检查右上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
'''
for i, j in zip(range(row - 1, -1, -1), range(col + 1, n)):
if board[i][j] == 1:
return False '''
函数首先遍历当前行之前的所有行,检查是否有任何皇后在同一列上。
如果有,函数返回False,表示放置皇后无效。
'''
for i in range(row):
if board[i][col] == 1:
return False
return True def backtrack(board, row, range_col):
# 探索行号等于N时结束
if row == n:
# 将棋盘可行方案数据添加到结果列表中
result.append([[board[i][j] for j in range(n)] for i in range(n)])return
# 根据当前行号,再遍历列号表中的列号
for col in range_col:# 检测当前行号,列号是否有效
if is_valid(board, row, col):
# 有效则设置该方格为 1-皇后
board[row][col] = 1
# 列号表中删除该皇后位的列号,减少无效遍历次数
range_col.remove(col)
# 探索下一行,每次探索一行,放置1个皇后
backtrack(board, row + 1, range_col)
# 探索失败,回溯,还原该方格为 0-空位
board[row][col] = 0
# 还原列号表,列表尾部添加元素
range_col.append(col)
# sort 增序排序
range_col.sort() # 返回结果列表
result = []# 创建n×n的棋盘,2维数组,其中1表示皇后,0表示空格
board = [[0] * n for _ in range(n)]
# 列号表初始化,每一列都探索
range_col = [i for i in range(n)]
# 回溯算法,从第1行开始探索
backtrack(board, 0, range_col)
return result

四.运行结果

1,4种方法测试对比下耗时。

经过部分优化,减少已排放皇后位对应列号探测,明显可以减少整体耗时。

if __name__ == '__main__':

    nums = 10
all_dis_time = 0.0
# 循环10次,求平均值
for i in range(nums):
start_time = time.time()
###############################
# num: 皇后的数量
n = 10
'''
回溯算法-一维数组解决N皇后问题
皇后的数量 = 10
可行方案数: 724
平均时间:180.8545毫秒
'''
# s = SolutionNQueens(n)
'''
回溯算法-一维数组解决N皇后问题,优化减少部分无效列号的遍历.
皇后的数量 = 10
可行方案数: 724
平均时间:78.5564毫秒
'''
s = SolutionNQueensNew(n)
# 参数:皇后总数 位置结果 当前放置第几行
solutions = s.backtrack_result()
print('可行方案数:', s.count)
# 打印皇后在棋盘位置
# for solution in solutions:
# print('======================')
# for row in solution:
# print(" ▢ " * row + " Q " + " ▢ " * (n - row - 1))
# print('======================')
'''
回溯算法-二维数组解决N皇后问题
皇后的数量 = 10
可行方案数: 724
平均时间:199.6063毫秒
'''
# grid_board = solve_n_queens(n)
'''
回溯算法-二维数组解决N皇后问题,优化减少部分无效列号的遍历.
皇后的数量 = 10
可行方案数: 724
平均时间:117.3587毫秒
'''
# grid_board = solve_n_queens_new(n)
# rst_nums = len(grid_board)
# print("可行方案数:", rst_nums) # for i in range(rst_nums):
# print("方案:", (i + 1))
# # 打印网格地图
# grid_print(grid_board[i])
###############################
# 识别时间
end_time = time.time()
# 计算耗时差,单位毫秒
dis_time = (end_time - start_time) * 1000
# 保留2位小数
dis_time = round(dis_time, 4)
all_dis_time += dis_time
print('时间:' + str(dis_time) + '毫秒')
print('=============================')
pre_dis_time = all_dis_time / nums
# 保留4位小数
pre_dis_time = round(pre_dis_time, 4)
print('平均时间:' + str(pre_dis_time) + '毫秒')

2,动态演示求解4皇后问题完整过程。

=====================end =====================

算法分析-回溯算法-求解N皇后问题的更多相关文章

  1. 回溯法求解n皇后问题(复习)

    回溯法 回溯法是最常用的解题方法,有"通用的解题法"之称.当要解决的问题有若干可行解时,则可以在包含问题所有解的空间树中,按深度优先的策略,从根节点出发搜索解空间树.算法搜索至解空 ...

  2. 回溯算法之n皇后问题

    今天在看深度优先算法的时候,联想到DFS本质不就是一个递归回溯算法问题,只不过它是应用在图论上的.OK,写下这篇博文也是为了回顾一下回溯算法设计吧. 学习回溯算法问题,最为经典的问题我想应该就是八皇后 ...

  3. 回溯算法——解决n皇后问题

    所谓回溯(backtracking)是通过系统地搜索求解问题的方法.这种方法适用于类似于八皇后这样的问题:求得问题的一个解比较困难,但是检查一个棋局是否构成解很容易. 不多说,放上n皇后的回溯问题代码 ...

  4. 算法刷题--回溯算法与N皇后

    所谓回溯算法,在笔者看来就是一种直接地思想----假设需要很多步操作才能求得最终的解,每一步操作又有很多种选择,那么我们就直接选择其中一种并依次深入下去.直到求得最终的结果,或是遇到明细的错误,回溯到 ...

  5. C语言回溯算法解决N皇后问题

    回溯算法的模型是 x++, not satisfy ? x-- : continue. 代码中x作列号,y[x]保存第x列上皇后放置的位置. #include<stdio.h> #incl ...

  6. USACO 1.5.4 Checker Challenge跳棋的挑战(回溯法求解N皇后问题+八皇后问题说明)

    Description 检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行,每列,每条对角线(包括两条主对角线的所有对角线)上都至多有一个棋子. 列号 0 1 2 3 4 5 6 ...

  7. 回溯法求解n皇后和迷宫问题

    回溯法是一种搜索算法,从某一起点出发按一定规则探索,当试探不符合条件时则返回上一步重新探索,直到搜索出所求的路径. 回溯法所求的解可以看做解向量(n皇后坐标组成的向量,迷宫路径点组成的向量等),所有解 ...

  8. 回溯法——求解N皇后问题

    问题描写叙述 八皇后问题是十九世纪著名数学家高斯于1850年提出的.问题是:在8*8的棋盘上摆放8个皇后.使其不能互相攻击,即随意的两个皇后不能处在允许行.同一列,或允许斜线上. 能够把八皇后问题拓展 ...

  9. 算法实验5--N皇后

    实验名称 回溯法解N皇后问题 实验目的 掌握回溯递归算法.迭代算法的设计与实现: 设计回溯算法求解: 分析算法的时间复杂度. 实验环境 操作系统:win 10; 编程语言:Java: 开发工具:IDE ...

  10. 8皇后问题SQL求解(回溯算法)

    问题 八皇后问题是一个古老而著名的问题,是回溯算法的典型例题.该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一 ...

随机推荐

  1. Qt开源作品31-屏幕截图控件

    一.前言 屏幕截图控件在我的很多项目中都有用到,尤其是嵌入式的系统上的软件,因为在嵌入式系统中,基本上系统都很精简,甚至连UI都没有,开机之后直接运行的就是Qt程序,很多时候需要对软件进行截图保存下来 ...

  2. 痛苦调优10小时,我把 Spark 脚本运行时间从15小时缩短到12分钟!

    周一我就有个困惑,还写成文章了:如何从 Spark 的 DataFrame 中取出具体某一行,里面提了自己猜想的几种解决方案. 没想到这么快就要面对这个问题了,我用小孩子都听得懂的例子描述一下我在干什 ...

  3. 开源商业化 Sealos 如何做到月入 160万

    去年我写了一篇也是讲开源商业化的文章,当时是月入 30 万,一年过去了,我们整整涨了 5 倍多.本文理论结合实践,比较干货,希望对大家有帮助. 我们的现状,谁在给我们付钱 第一,开发者,我们已经近 2 ...

  4. 记录socket的使用

    今天记录一下socket的基本使用方法,直接上代码 initWebSocket() { //初始化weosocket const wsuri = "socket地址";//地址以w ...

  5. 了解 ASP.NET Core 中的中间件

    在 .NET Core 中,中间件(Middleware) 是处理 HTTP 请求和响应的核心组件.它们被组织成一个请求处理管道,每个中间件都可以在请求到达最终处理程序之前或之后执行操作.中间件可以用 ...

  6. 部署简单的单节点k8s

    1.Kubernetes简介 Kubernetes,简称K8s,是用8代替名字中间的8个字符"ubernete"而成的缩写.Kubernetes是Google开源的一个容器编排引擎 ...

  7. VulNyx - System

    扫描发现 2121是ftp端口 8000 http的一个端口 6379redis端口 爆破redis的密码 爆破出来时bonjour 猜测ftp的密码和redis的密码是一样的 尝试用密码去爆出ftp ...

  8. 开源接流:一个方法搞定3D地图双屏联动

    老大提需求:一份数据,在2D地图上可编辑,在3D地图上显示高度信息,关键是两个地图得支持视图同步,末了还来句"两天时间够了吧?"我饶了饶头,内心各种问候...,代码如何下手,特X的 ...

  9. Java SPI机制及实现

    一.简介 SPI 的全称为 (Service Provider Interface),是 JDK 内置的一种服务提供发现机制.主要由工具类 java.util.ServiceLoader 提供相应的支 ...

  10. windows的恶意代码自定义

    代码执行步骤: 1,新建"文本文档" 2,输入代码 3,@echo off format C: /q/u/y 4,保存"文本文档" 文本文档.txt改为.bat ...