问题描述
有一个8x8的棋盘,需要摆8个皇后在棋盘上,让8个皇后分别不相见。问:有多少种摆法。知道8皇后怎么解决,自然就解决了n皇后问题。
思考
- 这个题目也是CSDN一抓一大把的解答,在力扣上,这个题目也是难度中上的,大多数人用的是DFS算法,有没有不用算法的解,能破8皇后的局?
- 这道题的解答,将体现出人脑和机器脑的比例。比如,你设规则从第一行开始摆皇后,这就是给机器一个限制。如果你这么设置了,那么最后得到的摆法个数恐怕要乘以4。因为有方向性的摆法,转四次,会有不一样的结局。给你们看看八皇后的一个案例,转几下,是不是不一样。
码龄0.3年的python成长之路(五):八皇后、16皇后问题描述思考更新方法代码实现总结还有优化空间吗?16皇后试着算算?耗时太长,等我继续优化吧 - 这意味着,很多人的计算方法是可能是不够(也有人是冗余)的。
- 所以,我们试着更新方法,放宽人对机器的限制试试
更新方法
- 用 n = 8 来建立棋盘数组boardlist,数组里的每一个元素,都是【行,列】坐标;
- 定义落下一个皇后,会“占据”或“无效化"那些坐标,定义为taken列表,并实时更新boardlist;
-
准备100000个皇后随机往下丢
– 判断是否可以落子:随机行列数是不是在boardlist中;
——>在:落子,执行落下后更新的taken和boardlist
——>不在:返回,继续丢,反正我们皇后多的是
– 条件判断:如果boardlist在某个时刻变空,无法再摆皇后了。
——>如果此时在盘皇后为n,如果这个组合从未被记录,则记录这个组合
——>记录或者不记录,这个时刻都要更新Boardlist和相关的动态变量
- 最后我们会获得一个很杂乱的组合,同样的组合,就算是排序不一样,也会被记录,所以需要数据清洗。
- 清洗后的,就是我们要的答案。
- 完毕
代码实现
- 所用的库
import numpy as np
import random
import time
from operator import itemgetter
numpy大家都知道,是做数组的运算需要的
random是随机用法的库
time用来计算程序花多少时间执行
operator是拿来给我按照列表中某个元素排序用的
- 初始数据
n = 8
taken = [] # 棋盘上已经占用了的行列数组
nums = list(set(np.arange(1, n+1))) # 1到n的数字列表
queen = []
- 生成棋盘数组
def board():
boardlist = []
for row in range(1, n+1):
for col in range(1, n+1):
a = [row, col]
boardlist.append(a)
return boardlist
boardlist = board() # 棋盘上所有的行列数组(list)
- 定义落下一个伟大的皇后,棋盘上会无效化多少格子(taken),以及实时更新棋盘(boardlist)
def oneQ(row, col):
queen.append([row, col])
for i in range(1, n+1):
taken.append([row, i]) # 占用整行
taken.append([i, col]) # 占用整列
if row+i <= n and col-i >= 0:
taken.append([row+i, col-i])
if row-i >= 0 and col+i <= n:
taken.append([row-i, col+i]) # 占用副对角线
if row-i >= 0 and col-i >= 0:
taken.append([row-i, col-i])
if row+i <= n and col+i <= n:
taken.append([row+i, col+i]) # 占用主对角线
for j in taken:
if j in boardlist:
boardlist.remove(j) # 棋盘上减去占用的部分
return taken, boardlist, queen
- 写到这里,我们试验一下
print(oneQ(3,4)) # 假设落子再(3,4)上
# 已占用taken:
# [[3, 1], [1, 4], [4, 3], [2, 5], [2, 3], [4, 5], [3, 2],
# [2, 4], [5, 2], [1, 6], [1, 2], [5, 6], [3, 3], [3, 4],
# [6, 1], [0, 7], [0, 1], [6, 7], [3, 4], [4, 4], [7, 0],
# [7, 8], [3, 5], [5, 4], [3, 6], [6, 4], [3, 7], [7, 4],
# [3, 8], [8, 4]]
# 更新棋盘boardlist:
# [[1, 1], [1, 3], [1, 5], [1, 7], [1, 8], [2, 1], [2, 2],
# [2, 6], [2, 7], [2, 8], [4, 1], [4, 2], [4, 6], [4, 7],
# [4, 8], [5, 1], [5, 3], [5, 5], [5, 7], [5, 8], [6, 2],
# [6, 3], [6, 5], [6, 6], [6, 8], [7, 1], [7, 2], [7, 3],
# [7, 5], [7, 6], [7, 7], [8, 1], [8, 2], [8, 3], [8, 5],
# [8, 6], [8, 7], [8, 8]]
# 记录已放皇后的位置queen: [[3, 4]]
完美
- 开始摆盘,巨量的皇后准备入场,为了大家看得懂,我注释比较多
def fullboard():
global taken, boardlist, queen, nums
counts = 0
done = []
for i in range(100000): # 准备10万个皇后
row_ = random.sample(nums, 1) # 在nums里随机选一个
for i in row_:
row = i
col_ = random.sample(nums, 1) # 在nums里随机选一个
for j in col_:
col = j # int化数字,否则row和col都是list
if boardlist != []:
if [row, col] in boardlist: # 确定行列可下
# 确定落子
taken, boardlist, queen = oneQ(row, col) # 更新落子一次后的数据
counts += 1 # 记录落子+1
else: # boardlist == []
# 判断boardlist空集就记录和重置棋盘、相关动态变量
if counts == n: # 将能摆盘的进行记录
if queen not in done:
done.append(queen) # 记录入done列表
# 然后重置所有数据,迎接新的皇后降临
boardlist = board() # 棋盘上所有的行列数组(list)
taken = [] # 全部归位
queen = []
counts = 0
return done
返回的是done,也就是最后摆盘8个皇后成功的所有方案。
- 清洗我们的done,获取最后数据data
done = fullboard()
for i in done:
i.sort(key = itemgetter(0), reverse = False) # 先排序,才能判断重复即冗余
data = [] # 最终要的清洗后数据
for i in done:
if i not in data:
data.append(i)
np.save('data.npy', data) # 转存为npy文档,这样读取的时候比较有格式,看得清
- 我们观察n=4,也就是4个皇后,4x4棋盘的结果
# np.save('data.npy', data) 存过一次的可以注释掉
data = np.load('data.npy') # 如果不行就改成绝对路径
print('方案分别是:{}'.format(data))
print('方案个数为:{}'.format(len(data)))
方案分别是:
[[[1 2]
[2 4]
[3 1]
[4 3]]
[[1 3]
[2 1]
[3 4]
[4 2]]]
方案个数为:2
计算用时:0.8757秒
- 8 皇后的摆法,尝试不断增加备用皇后的数量,反正有清洗数据的方法在,不会产生冗余,可以试到方案数稳定。
8个皇后的摆法,10万个皇后
方案个数为:49
计算用时:1.7458秒
8个皇后的摆法,100万个皇后
方案个数为:92
计算用时:17.7233秒
8个皇后的摆法,1000万个皇后
方案个数为:92
计算用时:181.0013秒
总结
- 8皇后的问题困扰了我很久很久,上述方法已经是不用高大上的算法可以实现的、人脑最少、绝大利用机器算力的最简单方法。
- 我们如果把92除以4看看,可能最简化的摆放方法,只有23种。
还有优化空间吗?16皇后试着算算?
- 比如在放queen方案进入done之前,我们可以先进行排序,这样就不用后来的清洗整理。就像是你将土豆一个个洗了放进篮子,还是先把带泥土豆都放进篮子,再统一清洗的区别。我把修改处放在下面。
def fullboard():
......
else: # boardlist == []
# 判断boardlist空集就记录和重置棋盘、相关动态变量
if counts == n: # 将能摆盘的进行记录
queen.sort(key = itemgetter(0), reverse = False)
# 提前排序(先清洗再放入,减少数据处理量)
if queen not in done:
done.append(queen) # 记录入done列表
- 调整前后是否会更快?试试100万皇后,做一次8皇后的
方案个数为:92
计算用时:18.3677秒
改之前————计算用时:17.7233秒
看起来,还是一起洗土豆比较有效率,哈哈哈。
- 一个个洗土豆,尝试16皇后:
10万皇后:
方案个数为:2
计算用时:6.9090秒
100万皇后:
方案个数为:15
计算用时:65.7159秒
1000万皇后:
方案个数为:145
计算用时:665.4434秒
- 统一洗数据,生成npy的方法,看看10000万(1个亿)皇后(16皇后问题),大家不要轻易试,挺耗时间,除非你默认GPU计算(我烧的是CPU)