当前位置:   article > 正文

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

回溯法求解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)判断,这样就不需要写两个公式。

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

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

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

4.算法分析

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

.编程实现

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

实现方法一:

  1. class SolutionNQueens(object):
  2. '''
  3. 回溯算法-一维数组解决N皇后问题。
  4. 该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
  5. '''
  6. def __init__(self, num):
  7. self.count = 0
  8. self.num = num
  9. # 校验当前行号,列号是否有效
  10. def is_valid(self, board, row, col):
  11. # 遍历行号
  12. for i in range(row):
  13. if board[i] == col or abs(board[i] - col) == abs(i - row):
  14. return False
  15. return True
  16. def backtrack(self, board, row, result):
  17. if row == self.num:
  18. result.append(board[:])
  19. self.count += 1
  20. return
  21. for col in range(self.num):
  22. if self.is_valid(board, row, col):
  23. board[row] = col
  24. self.backtrack(board, row + 1, result)
  25. board[row] = 0
  26. def backtrack_result(self):
  27. result = []
  28. # 最终皇后的位置 (下标:第几行 数值:第几列)
  29. board = [0] * self.num
  30. # 从第一行开始
  31. row = 0
  32. self.backtrack(board, row, result)
  33. return result
'
运行

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

实现方法二:

  1. class SolutionNQueensNew(object):
  2. '''
  3. 回溯算法-一维数组解决N皇后问题,优化减少部分无效列号的遍历。
  4. 该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
  5. '''
  6. def __init__(self, num):
  7. self.count = 0
  8. self.num = num
  9. # 校验当前行号,列号是否有效
  10. def is_valid(self, board, row, col):
  11. # 遍历行号
  12. for i in range(row):
  13. if board[i] == col or abs(board[i] - col) == abs(i - row):
  14. return False
  15. return True
  16. def backtrack(self, board, row, range_col, result):
  17. if row == self.num:
  18. result.append(board[:])
  19. self.count += 1
  20. return
  21. # 根据当前行号,再遍历列号表中的列号
  22. for col in range_col:
  23. if self.is_valid(board, row, col):
  24. # 有效则设置该位为 皇后
  25. board[row] = col
  26. # 列号表中删除该皇后位的列号,减少无效遍历次数
  27. range_col.remove(col)
  28. # 探索下一行,每次探索一行,放置1个皇后
  29. self.backtrack(board, row + 1, range_col, result)
  30. # 探索失败,回溯,还原该位置为 0-空位
  31. board[row] = 0
  32. # 还原列号表,列表尾部添加元素
  33. range_col.append(col)
  34. # sort 增序排序
  35. range_col.sort()
  36. def backtrack_result(self):
  37. result = []
  38. # 最终皇后的位置 (下标:第几行 数值:第几列)
  39. board = [0] * self.num
  40. # 从第一行开始
  41. row = 0
  42. # 列号表初始化,每一列都探索
  43. range_col = [i for i in range(self.num)]
  44. self.backtrack(board, row, range_col, result)
  45. return result
'
运行

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

实现方法三:

  1. def solve_n_queens(n):
  2. '''
  3. 回溯算法-二维数组解决N皇后问题
  4. 该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
  5. '''
  6. def is_valid(board, row, col):
  7. '''
  8. board(一个二维列表,表示棋盘),
  9. row(一个整数,表示要检查的行索引),
  10. col(一个整数,表示要检查的列索引)。
  11. 函数的目的是检查在给定的行和列上放置一个皇后是否有效。
  12. '''
  13. '''
  14. 函数首先遍历当前行之前的所有行,检查是否有任何皇后在同一列上。
  15. 如果有,函数返回False,表示放置皇后无效。
  16. '''
  17. for i in range(row):
  18. if board[i][col] == 1:
  19. return False
  20. '''
  21. zip循环检查左上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
  22. '''
  23. for i, j in zip(range(row - 1, -1, -1), range(col - 1, -1, -1)):
  24. if board[i][j] == 1:
  25. return False
  26. '''
  27. zip循环检查右上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
  28. '''
  29. for i, j in zip(range(row - 1, -1, -1), range(col + 1, n)):
  30. if board[i][j] == 1:
  31. return False
  32. return True
  33. def backtrack(board, row):
  34. # 探索行号等于N时结束
  35. if row == n:
  36. # 将棋盘可行方案数据添加到结果列表中
  37. result.append([[board[i][j] for j in range(n)] for i in range(n)])return
  38. # 根据当前行号,再遍历列号
  39. for col in range(n):# 检测当前行号,列号是否有效
  40. if is_valid(board, row, col):
  41. # 有效则设置该方格为 1-皇后
  42. board[row][col] = 1
  43. # 探索下一行,每次探索一行,放置1个皇后
  44. backtrack(board, row + 1)
  45. # 探索失败,回溯,还原该方格为 0-空位
  46. board[row][col] = 0
  47. # 返回结果列表
  48. result = []# 创建n×n的棋盘,2维数组,其中1表示皇后,0表示空格
  49. board = [[0] * n for _ in range(n)]
  50. # 回溯算法,从第1行开始探索
  51. backtrack(board, 0)
  52. return result

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

实现方法四:

  1. def solve_n_queens_new(n):
  2. '''
  3. 回溯算法-二维数组解决N皇后问题,优化减少部分无效列号的遍历。
  4. 该算法的时间复杂度为:O(n!),因为总共有n!种可能的摆放方式。空间复杂度:O(n),用于存储递归调用栈。
  5. '''
  6. def is_valid(board, row, col):
  7. '''
  8. board(一个二维列表,表示棋盘),
  9. row(一个整数,表示要检查的行索引),
  10. col(一个整数,表示要检查的列索引)。
  11. 函数的目的是检查在给定的行和列上放置一个皇后是否有效。
  12. '''
  13. '''
  14. zip循环检查左上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
  15. '''
  16. for i, j in zip(range(row - 1, -1, -1), range(col - 1, -1, -1)):
  17. if board[i][j] == 1:
  18. return False
  19. '''
  20. zip循环检查右上对角线上的单元格。如果在这些单元格中找到一个皇后,函数同样返回False。
  21. '''
  22. for i, j in zip(range(row - 1, -1, -1), range(col + 1, n)):
  23. if board[i][j] == 1:
  24. return False
  25. '''
  26. 函数首先遍历当前行之前的所有行,检查是否有任何皇后在同一列上。
  27. 如果有,函数返回False,表示放置皇后无效。
  28. '''
  29. for i in range(row):
  30. if board[i][col] == 1:
  31. return False
  32. return True
  33. def backtrack(board, row, range_col):
  34. # 探索行号等于N时结束
  35. if row == n:
  36. # 将棋盘可行方案数据添加到结果列表中
  37. result.append([[board[i][j] for j in range(n)] for i in range(n)])return
  38. # 根据当前行号,再遍历列号表中的列号
  39. for col in range_col:# 检测当前行号,列号是否有效
  40. if is_valid(board, row, col):
  41. # 有效则设置该方格为 1-皇后
  42. board[row][col] = 1
  43. # 列号表中删除该皇后位的列号,减少无效遍历次数
  44. range_col.remove(col)
  45. # 探索下一行,每次探索一行,放置1个皇后
  46. backtrack(board, row + 1, range_col)
  47. # 探索失败,回溯,还原该方格为 0-空位
  48. board[row][col] = 0
  49. # 还原列号表,列表尾部添加元素
  50. range_col.append(col)
  51. # sort 增序排序
  52. range_col.sort()
  53. # 返回结果列表
  54. result = []# 创建n×n的棋盘,2维数组,其中1表示皇后,0表示空格
  55. board = [[0] * n for _ in range(n)]
  56. # 列号表初始化,每一列都探索
  57. range_col = [i for i in range(n)]
  58. # 回溯算法,从第1行开始探索
  59. backtrack(board, 0, range_col)
  60. return result

.运行结果

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

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

  1. if __name__ == '__main__':
  2. nums = 10
  3. all_dis_time = 0.0
  4. # 循环10次,求平均值
  5. for i in range(nums):
  6. start_time = time.time()
  7. ###############################
  8. # num: 皇后的数量
  9. n = 10
  10. '''
  11. 回溯算法-一维数组解决N皇后问题
  12. 皇后的数量 = 10
  13. 可行方案数: 724
  14. 平均时间:180.8545毫秒
  15. '''
  16. # s = SolutionNQueens(n)
  17. '''
  18. 回溯算法-一维数组解决N皇后问题,优化减少部分无效列号的遍历.
  19. 皇后的数量 = 10
  20. 可行方案数: 724
  21. 平均时间:78.5564毫秒
  22. '''
  23. s = SolutionNQueensNew(n)
  24. # 参数:皇后总数 位置结果 当前放置第几行
  25. solutions = s.backtrack_result()
  26. print('可行方案数:', s.count)
  27. # 打印皇后在棋盘位置
  28. # for solution in solutions:
  29. # print('======================')
  30. # for row in solution:
  31. # print(" ▢ " * row + " Q " + " ▢ " * (n - row - 1))
  32. # print('======================')
  33. '''
  34. 回溯算法-二维数组解决N皇后问题
  35. 皇后的数量 = 10
  36. 可行方案数: 724
  37. 平均时间:199.6063毫秒
  38. '''
  39. # grid_board = solve_n_queens(n)
  40. '''
  41. 回溯算法-二维数组解决N皇后问题,优化减少部分无效列号的遍历.
  42. 皇后的数量 = 10
  43. 可行方案数: 724
  44. 平均时间:117.3587毫秒
  45. '''
  46. # grid_board = solve_n_queens_new(n)
  47. # rst_nums = len(grid_board)
  48. # print("可行方案数:", rst_nums)
  49. # for i in range(rst_nums):
  50. # print("方案:", (i + 1))
  51. # # 打印网格地图
  52. # grid_print(grid_board[i])
  53. ###############################
  54. # 识别时间
  55. end_time = time.time()
  56. # 计算耗时差,单位毫秒
  57. dis_time = (end_time - start_time) * 1000
  58. # 保留2位小数
  59. dis_time = round(dis_time, 4)
  60. all_dis_time += dis_time
  61. print('时间:' + str(dis_time) + '毫秒')
  62. print('=============================')
  63. pre_dis_time = all_dis_time / nums
  64. # 保留4位小数
  65. pre_dis_time = round(pre_dis_time, 4)
  66. print('平均时间:' + str(pre_dis_time) + '毫秒')

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/1013987
推荐阅读
相关标签
  

闽ICP备14008679号