算法分析-回溯算法-求解N皇后问题

一.题目需求

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 Falsereturn 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 = 0self.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 Falsereturn Truedef backtrack(self, board, row, result):if row == self.num:result.append(board[:])self.count += 1returnfor col in range(self.num):if self.is_valid(board, row, col):board[row] = colself.backtrack(board, row + 1, result)board[row] = 0def backtrack_result(self):result = []# 最终皇后的位置 (下标:第几行 数值:第几列)board = [0] * self.num# 从第一行开始row = 0self.backtrack(board, row, result)return result

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

实现方法二:

class SolutionNQueensNew(object):'''回溯算法-一维数组解决N皇后问题,优化减少部分无效列号的遍历。该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。'''def __init__(self, num):self.count = 0self.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 Falsereturn Truedef backtrack(self, board, row, range_col, result):if row == self.num:result.append(board[:])self.count += 1return# 根据当前行号,再遍历列号表中的列号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 Falsereturn Truedef 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 Falsereturn Truedef 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 = 10all_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_timeprint('时间:' + 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 =====================

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/307103.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Element UI之el-tabs的样式修改字体颜色、下划线、选中/未选中

目录 默认样式 修改默认字体颜色&#xff1a; 修改鼠标悬浮/选中字体颜色&#xff1a; 去掉长分割线并修改下划线颜色 完整代码 默认样式 注意事项&#xff1a;一定要在 <style scoped>不然修改的样式不会覆盖生效 修改默认字体颜色&#xff1a; ::v-deep .el-tabs__…

自动化测试po模式是什么?自动化测试po分层如何实现?

一、什么是PO模式 全称&#xff1a;page object model 简称&#xff1a;POM/PO PO模式最核心的思想是分层&#xff0c;实现松耦合&#xff01;实现脚本重复使用&#xff0c;实现脚本易维护性&#xff01; 主要分三层&#xff1a; 1.基础层BasePage&#xff1a;封装一些最基…

基于ssm网月科技公司门户网站开发与实现论文

网月科技公司门户网站 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了网月科技公司门户网站的开发全过程。通过分析高校学生综合素质评价管理方面的不足&#xff0c;创建了一个计算机管理网月科技公司门户网站…

【SpringBoot篇】详解Bean的管理(获取bean,bean的作用域,第三方bean)

文章目录 &#x1f354;Bean的获取&#x1f384;注入IOC容器对象⭐代码实现&#x1f6f8;根据bean的名称获取&#x1f6f8;根据bean的类型获取&#x1f6f8;根据bean的名称和类型获取 &#x1f384;Bean的作用域⭐代码实现&#x1f388;注意 &#x1f384;第三方Bean⭐代码实现…

PayPal账户被封是因为什么?如何解决?

Paypal作为跨境出海玩家最常用的付款工具之一&#xff0c;同时也是最容易出现冻结封号现象。保障PP账号安全非常重要&#xff0c;只有支付渠道安全&#xff0c;才不会“白费力气”&#xff0c;那么最重要的就是要了解它的封号原因以做好规避。 一、Paypal账号被封原因 1、账号…

说说 Spring MVC 的执行流程?

昨天&#xff0c;一个工作 2 年的粉丝在面试的时候&#xff0c;面试官要求他说 Spring MVC 的执行流程。 他没回答上来&#xff0c;错过了这个 offer。 这个问题在我之前整理的大厂面试指南里面&#xff0c;有标准的回答&#xff0c;大家可以去文章结尾中领取。 一、问题解析…

蓝桥杯单片机国一分享——你的蓝桥杯单片机第一课,如何入门与如何备赛

国一证书 第一章 获取信息的途径 其实任何教程或者任何网站都都不可能做得尽善尽美&#xff0c;甚至是官方的也是如此&#xff0c;也不可能适合每个人&#xff0c;所以我首先向你们提供我在备赛时使用到的几个有用的网站&#xff0c; 官网&#xff1a;https://dasai.lanqiao.cn…

Android Context在四大组件及Application中的表现

文章目录 Android Context在四大组件及Application中的表现Context是什么Context源码Activity流程分析Service流程分析BroadcastReceiver流程分析ContentProvider流程分析Application流程分析 Android Context在四大组件及Application中的表现 Context是什么 Context可以理解…

C++ //例13.14 将一批数据以二进制形式存放在磁盘文件中。例13.15 将刚才以二进制形式存放在磁盘文件中的数据读入内存并在显示器上显示。

C程序设计 &#xff08;第三版&#xff09; 谭浩强 例13.14 例13.15 例13.14 将一批数据以二进制形式存放在磁盘文件中。 例13.15 将刚才以二进制形式存放在磁盘文件中的数据读入内存并在显示器上显示。 IDE工具&#xff1a;VS2010 Note: 使用不同的IDE工具可能有部分差异。…

TiDB故障处理之让人迷惑的Region is Unavailable

背景 最近某集群扩容了一批物理机&#xff0c;其中 TiKV 节点有6台机器12个实例&#xff0c;同时调整了 label 设置增加了一层机柜级容灾。因为前期做了比较充分的准备工作&#xff0c;到了变更窗口只等着执行scale-out就行&#xff0c;操作过程也很顺利&#xff0c;很快就把所…

Lumerical------按键中断程序执行

Lumerical------中断程序执行 引言正文 引言 在 Lumerical 中&#xff0c;很多时候我们需要通过 sweep 的方式来获取我们想要的结果&#xff0c;然而&#xff0c;有时候当我们运行程序后发现书写的脚本有问题时&#xff0c;我们想要强行终止程序的执行&#xff0c;该怎么办呢&…

C#中的Attribute详解(上)

C#中的Attribute详解&#xff08;上&#xff09; 一、Attribute是什么二、Attribute的作用三、Attribute与注释的区别四、系统Attribute范例1、如果不使用Attribute&#xff0c;为了区分这四类静态方法&#xff0c;我们只能通过注释来说明&#xff0c;但这样做会给系统带来很多…