Python 回溯法 子集樹模組系列 八皇后問題

2021-09-24 22:00:00 字數 4692 閱讀 9164

案例

八皇后問題是乙個以西洋棋為背景的問題:如何能夠在 8×8 的西洋棋棋盤上放置八個皇后,使得任何乙個皇后都無法直接吃掉其他的皇后?為了達到此目的,任兩個皇后都不能處於同一條橫行、縱行或斜線上。八皇后問題可以推廣為更一般的n皇后擺放問題:這時棋盤的大小變為n×n,而皇后個數也變成n。當且僅當 n = 1 或 n ≥ 4 時問題有解。

分析一

關於皇后衝突的判定

用自然語言很容易描述八個皇后的位置制約關係,即棋盤的每一行,每一列,每乙個條正斜線,每一條反斜線,都只能有1個皇后。如果用這個方法,判斷新加入的皇后位置是否與已經存在的皇后位置衝突,先求出新皇后在哪一行,列,正反兩條斜線上,再依次判斷是否衝突。我們先用圖表對棋盤進行分析。

其中,每一行只能有乙個皇后,且列不能重複。不管是「/」還是「\」,橫縱座標之差的絕對值即為行序號或列序號之差。

對上述判定進行程式化,如下所示:

#衝突檢查,在定義state時,採用state來標誌每個皇后的位置,其中索引用來表示橫座標,基對應的值表示縱座標,例如: state[0]=3,表示該皇后位於第1行的第4列上

def conflict(state, nextx):

nexty = len(state)

for i in range(nexty):

#如果下乙個皇后的位置與當前的皇后位置相鄰(包括上下,左右)或在同一對角線上,則說明有衝突,需要重新擺放

if abs(state[i]-nextx) in (0, nexty-i):

return true

return false

遞迴函式

#採用生成器的方式來產生每乙個皇后的位置,並用遞迴來實現下乙個皇后的位置。

def queens(num, state=()):

for pos in range(num):

if not conflict(state, pos):

print(state,pos)

#產生當前皇后的位置資訊

if len(state) == num-1:

yield (pos, )

#否則,把當前皇后的位置資訊,新增到狀態列表裡,並傳遞給下一皇后。

else:

for result in queens(num, state+(pos,)):

print('result:',end='')

print(result)

yield (pos, ) + result

為了便於理解,在程式中加了輸出語句,執行print(list(queens(8,(0, 4, 7, 5))))這裡我們嘗試對輸出結果進行分析。

(0, 4, 7, 5) 2

(0, 4, 7, 5, 2) 6

(0, 4, 7, 5, 2, 6) 1

(0, 4, 7, 5, 2, 6, 1) 3

result:(3,)

result:(1, 3)

result:(6, 1, 3)

(0, 4, 7, 5) 3

(0, 4, 7, 5, 3) 1

(0, 4, 7, 5, 3) 6

[(2, 6, 1, 3)]

先大概說下 yield,它類似於 return,但和 return 不同的是 return 返回乙個值(這個「值」可以是數值,字串,序列等,但只是一次乙個),然後函式就結束了,而 yield 某個值後函式不會結束,而是繼續找出接下來所有符合條件的值,然後才結束。

根據結果可知,當 state 中值為(0,4,7,5)時,第一次進入遞迴函式中,發現 pos=2 通過判定檢查,但是由於 len(state) 長度不滿足條件,則繼續進入到遞迴函式,第二次發現 pos=6 符合,接著繼續進行遞迴,第三次發現 pos=1 符合,接著進行遞迴,第四次發現 pos=3 復合,此時滿足 len(state)==7條件,返回值 (3,) ,第四次遞迴函式返回 (3,)。則for result in queens(num, state+(pos,))語句第一次輸出便為 (3,);同時返回 (1,3) 值,第三次遞迴函式返回 (1,3),此為 for 迴圈的第二次輸出;同理,第二次遞迴函式返回 (6,1,3)。此時 pos=2,(2,6,1,3)為乙個結果,但是還未結束。回溯到第一次遞迴函式後,繼續進行判斷,發現 pos=3 也通過判定檢查,同上述一樣,一直到第四次遞迴函式中,沒有找到符合要求的值,所以層層向上返回時,未得到結果 。最終結果為 (2,6,1,3)。

這裡只是針對這種給定資料的情況進行分析,實際過程中會更加複雜,但是希望能通過上述分析,對**部分不再迷惑。

分析二

首先列出**,然後再進行分析。

n = 8

v = [[0] * 8 for x in range(9)]#棋盤列表

temp = [0]*9 #標誌每個皇后的位置,其中索引用來表示橫座標,基對應的值表示縱座標,例如:temp[0]=0表示該皇后位於第一行第一列上

def conflict(num):

for i in range(num):

# 判斷列和對角線,如果第num+1行上皇后的位置的列數與之前的皇后所在位置的列數相同,又或者當前皇后的位置的列數與之前皇后位置列數做差的絕對值等於行數做差,則說明有衝突,需要重新擺放

if (temp[num] == temp[i] or abs(temp[i] - temp[num]) == num - i):

return false

return true

def print_v(v):

#輸出八皇后列表

for i in range(n):

print(v[i])

def dfs(num):

if num >=n:

print_v(v)

print('***********' * 5)

for i in range(n):

temp[num] = i #當前皇后的位置設定為第num+1行,第i+1列

if v[num][i] != 1 and conflict(num):

v[num][i] = 1

dfs(num+1)

v[num][i] = 0

if __name__ == '__main__':

dfs(0)

邏輯分析:

遞迴指的是 dfs()方法,目標為if num >=n: print_v(v),無路可走指的是if v[num][i] != 1 and conflict(num)條件不通過,回溯指的是v[num][i] = 0

首先明確上述內容,我們再來研究**部分。其實和分析一相似(畢竟都是 dfs 演算法),當從第一行開始給定皇后的位置,然後深處查詢第二行、、、一直到第八行,當第八行資料找到之後,我們看到會繼續進入 dfs() 方法中,此時 num 值為 8,找到目標,但是程式會繼續找下去,但是if v[num][i] != 1 and conflict(num)判定過不去,所以會向上回溯,將第八行的皇后位置置為 0,再查詢是否存在第二個合適的位置,沒有則繼續向上回溯,將第七行的皇后位置置為 0,然後查詢合適的位置,以此類推,直到找到所有的值集。

這裡宣告一下 v 和 temp 的大小設定為 9 的原因,主要是因為找到目標後,**還會往下執行,temp[num] 和 v[num][i] 還需要進行衝突判定校驗,為了避免程式報錯,需要這樣宣告。

分析三

n = 8

results = #一組解

ans = #乙個解,每個元素的索引值為皇后所在的行數,元素值為皇后所在的列數

def conflict(k):

'''衝突檢查

:param k: 第k行,即ans中第k個元素

:return:

'''if len(set(ans)) != k+1:#皇后所在列數不允許存在相同的情況

return false

for i in range(k):

if abs(ans[k]-ans[i]) == abs(k-i):#當前皇后的位置的列數與之前皇后位置列數做差的絕對值等於行數做差,則說明有衝突,需要重新擺放

return false

return true

def dfs(k):

global results

if k >= n:

else:

for i in range(n):

if conflict(k):#剪枝

dfs(k+1)

ans.pop()#回溯

def show(ans):

for i in ans:

print('0'*i+'1'+'0'*(n-i-1))

dfs(0)

print(results)

show(results[0])

對於八皇后問題的求解,前兩種方法都較為複雜,不易於理解(當時花費很多時間分析**執行流程)。分析三是在學習回溯法詳解後,根據自己的理解構建的**,更加直觀。

回溯法 子集和問題

問題 給定n個正整數wi和乙個正整數m,在這n個正整數中找出乙個子集,使得子集中的正整數之和等於m。解的形式 設定乙個n元組 x0,x1,xn 1 如果wi包含在這個子集中,xi就等於1,反之等於0.boundfunction 演算法偽 package com.iteye.caoruntao.sum...

回溯演算法 子集II

思路 該題為子集問題,與之前 組合總和問題ii 的去重思想一致,即相同一層不能有相同的元素,因此去重邏輯 if i startidex nums i nums i 1 不變,注意要先排序,將相同元素放在一起 class solution void backtrack vector int nums,...

python 用回溯法(子集樹)解n皇后問題

n皇后問題 如何能夠在 n n 的西洋棋棋盤上放置n個皇后,使得任何乙個皇后都無法直接吃掉其他的皇后。為了達到此目的,任兩個皇后都不能處於同一條橫行 縱行或斜線上。def backtrack t,n 核心 if t n output return else for i in range n a t ...