藍橋杯2n皇后問題
問題
問題描述
給定一個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
這個就是生成器,有這個的函數,產生的結果將會作爲可迭代對象調用。當函數遇到yield之後會停止運行,注意,函數只是停止了而沒有結束,這個和return是不一樣的。yield前面的功能和參數依然可以調用,參與計算。
我們來看下面的例子:
def generate():
yield 1
yield 2
這個函數我們可以把它抽象成[1,2]沒錯,他就是通過yield變成了可迭代對象,但它不同於列表,我們在這裏只是想讓讀者更好地明白這個yield到底在幹什麼。
現在我們先不說2n皇后,我們把重心先放在八皇后。
八皇后問題python解決
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皇后
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一開始是空的,所以加了個異常處理。