问题描述
给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。
输入格式
输入的第一行为一个整数n,表示棋盘的大小。
接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。
输出格式
输出一个整数,表示总共有多少种放法。
样例输入
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
2
样例输入
4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
0
首先们要知道八皇后问题是怎么解决的。我们通过递归回溯的方法,利用生成器为棋盘上的每一个棋子进行排序,然后将合法的数据记录下来。其中的yield函数可能不好理解,我们在下面介绍它
这个就是生成器,有这个的函数,产生的结果将会作为可迭代对象调用。当函数遇到yield之后会停止运行,注意,函数只是停止了而没有结束,这个和return是不一样的。yield前面的功能和参数依然可以调用,参与计算。
我们来看下面的例子:
def generate():
yield 1
yield 2
这个函数我们可以把它抽象成[1,2]没错,他就是通过yield变成了可迭代对象,但它不同于列表,我们在这里只是想让读者更好地明白这个yield到底在干什么。
现在我们先不说2n皇后,我们把重心先放在八皇后。
def conflict(queen_lis, new_queen):
"""判断棋子是否符合规则"""
for index, queen in enumerate(queen_tup):
if abs(new_queen - queen) in (len(queen_tup) - index, 0): # 判断表达式:垂直距离和水平距离
# 列数之差是否等于行数之差,或者列数之差为零
# 注意,len(queen_tup)是行数。因为第一行不进入循环,所以,这里的行数要比len出来的值加一,也就是,len(queen_tup)如果等于3,那么对应的是第四行
# 对应的,index也是要加一的。
return False
return True
def arrange_queen(num, queen_lis=[]):
"""
:param num:棋盘的的行数,当然数值也等于棋盘的列数
:param queen_tup: 设置一个空队列,用于保存符合规则的棋子的信息
"""
for new_queen in range(num): # 遍历一行棋子的每一列
if conflict(queen_tup, new_queen): # 判断是否冲突
if len(queen_tup) == num - 1: # 判断是否是最后一行
yield [new_queen] # yield关键字,返回当前的位置。
else:
# 若果不是最后一行,递归函数接着放置棋子
for result in arrange_queen(num, queen_lis + [new_queen]):# 结果的迭代
yield [new_queen] + result # 这个是每个函数最终的地方。
for i in arrange_queen(4):
print(i)
读者可以看代码旁的注释进一步的理解,我在这再解释一下这个递归回溯到底是怎么最终产生结果的:
当函数的conflict函数返回True之后,我们会判断这个是不是在排棋盘中的最后一行,如果是的话,就说明前面三行已经排好,并且,最后一行也是合法的,这个时候,最后一次递归结束,并将结果返回给上一层递归,上一层递归收到返回值之后把它作为了可迭代对象传给result,然后此时的new_queen就是上一层函数的参数,result就是最后一层递归返回的结果。相加后又yield给了上上层递归。这个就是回溯的过程。
读者可能会疑惑,棋盘明明是二维数组,为什么这里产生的结果都是一维的,这里我做解释:
我们这行数是通过递归来判断的,也就是说我们通过递归的次数来代替了行数,每递归一次行数就加一,同时通过返回queen_lis的长度来判断当前到了第几行,因为queen_lis里面加一个参数,就说明这个参数是合法的,此时参数保留,只要不是最后一层,那么就会进入下一次递归,同时这个参数就被加到了queen_lis里面。所以这个列表的长度就是我们要的行数。这个行数的判断极其的重要,因为之后另一个皇后的判断,我们又要利用到当前的行数。
上面是以四乘四的棋盘为例子举例的,了解以上的方法后,我们这个八皇后问题就解决了。
现在我们来探讨2n皇后
nums = eval(input())
n = 0
list_empty = []
while n < nums:
hang = [int(i) for i in input().split()]
list_empty.append(hang)
n +=1
def conflict(queen_list,new_queen,black=None):
num = len(queen_list)
try:
col = black[num]
except:
pass
try:
if len(black) != 0:
if new_queen == col:
return True
except:
pass
if list_empty[num][new_queen] == 0:
return True
for index,queen in enumerate(queen_list):
if abs(new_queen-queen) in (0,num-index):
return True
return False
def queen(num, queen_list=[]):
for new_queen in range(num):
if not conflict(queen_list, new_queen):
if len(queen_list) == num -1:
yield [new_queen]
else:
for result in queen(num,queen_list+[new_queen]):
yield [new_queen]+result
black_queen = list(queen(nums))
def queen_white(num,black,queen_list=[]):
for new_queen in range(num):
if not conflict(queen_list,new_queen,black):
if len(queen_list) == num -1:
yield [new_queen]
else:
for result in queen_white(num,black,queen_list+[new_queen]):
yield [new_queen]+result
end_num = []
sum = 0
for black in black_queen:
white_queens = list(queen_white(nums,black))
if len(white_queens)==0:
continue
end_num.append(len(white_queens))
for i in end_num:
sum +=i
print(sum)
现在,我们先排黑皇后再排白皇后。此时,我们需要两个排列皇后的函数,但是判断冲突的条件也要改一下,来适应和白皇后不同的冲突需求。
我们先排好黑皇后,将黑皇后的位置信息保存在black_queen
里面,接下来,我们开始排白皇后的位置,注意,白皇后位置的总和,就是题目的答案,这个应该很好理解,因为是在黑皇后位置的基础上,如此。
在上面的代码中,我们在conflict函数里面加了一个black,这个就是黑皇后的位置参数。我们通过在冲突判断函数中传入black参数,从而可以判断当前白皇后的位置是否和黑皇后的位置冲突。
同时,我们加了个棋盘是否为0的判断,用来判断当前位置是否合法。
queen_white
中我也传入了black参数,就是为了不用每次都定义一遍冲突函数,直接在调用排序的时候就可以自动传入参数。
接下来,就是通过for循环,将黑皇后的位置不停地传入判断黑皇后的位置函数里面,排序白皇后。
最后,将排序下来的次数存入到空列表中,最后把它们相加,最终打印出来。
我们发现,这2n皇后的解决,是基于八皇后的思想基础上的,代码上也就加了一个黑皇后位置参数判断,和棋盘合法性判断,其它并没有什么实质性的改变。
读者可能注意到了里面有异常处理,原因是因为本来应该是定义两个冲突函数的,但是这里我把他们合并了,变成了一个,所以,black一开始是空的,所以加了个异常处理。