少說話多寫程式碼之Python學習055——類的成員(生成器的應用舉例)
我們來看一個有趣的問題:八皇后問題。這裡的皇后是國際象棋中的皇后,雖然我只會玩中國象棋而不會玩國際象棋。這個問題和會不會國際象棋沒有關係。
八皇后問題描述:如何能夠在 8×8 的國際象棋棋盤上放置八個皇后,使得任何一個皇后都無法直接吃掉其他的皇后?為了達到此目的,任兩個皇后都不能處於同一條橫行、縱行或斜線上。
解決這個問題前,我們引入一個回溯的概念。比如我們走迷宮,前路未可知,向前走總會碰到岔路且是二選一的,我們一路選擇岔路,直到發現無法走通,就回到倒數第二次的岔路繼續二選一。如此往復,理論上我們最終總能走出迷宮。
那麼,八皇后問題,我們依然這樣解決。首先嚐試放置第一個皇后,在第一行。然後放置第二個皇后,一次類推。如果發現不能放置下一個皇后,就回溯到上一步。
下面我們開始程式碼實現。
我們定義是否下一個皇后放的位置是否合法。我們用陣列指定每一行皇后的位置。比如state[0]=2,表示第1行第3列的位置。我們用下面的函式表示下一個皇后放置的位置是否是正確的。
def confict(state,nextX): nextY = len(state) for i in range(nextY): if abs(state[i]-nextX) in (0,nextY-i): return True return False
nextX表示下一個皇后的水平位置,即x座標。nextY表示垂直位置,即y座標。對這兩個皇后的位置做一個檢查,如果下一個皇后和前面的皇后同樣有同樣的水平位置,或者是在一條對角線上,就表示不合法,返回True。如果檢查沒問題,返回False。之所以這樣返回,是因為這個函式的意思本地放的位置錯誤。True表示真的錯了,False表示沒毛病。
關鍵的一句是:abs(state[i]-nextX) in (0,nextY-1)。下一個皇后和前一個皇后水平距離為0,或者垂直距離為0,都表示有錯誤。
關鍵的程式碼來了,
def queens(num=8,state=()): for pos in range(num): if not confict(state,pos): if len(state) == num-1: yield (pos,) else: for result in queens(num,state+(pos,)): yield (pos,)+result
回溯的問題一定是要遞迴來實現,才比較方便。假定最後一個皇后的位置是正確的,需要回溯到上一步,在前面的步驟中加入if else選擇位置。這裡是最難理解的。
我們先這樣思考,當7個皇后都放好了位置,只剩最後一個皇后了,此時有兩種情況:一是,根據前7個皇后能生成出最後一個皇后的所有位置。二是最後一個皇后沒地方放了。
如果是情況一,那就解決問題了,此時事情做完了。如果是情況二,那麼就要回溯到第七個皇后的位置問題了。第七個皇后需要重新放一次不同位置。
呼叫如下,
print(len(list(queens(3))))
print(len(list(queens(4))))
print(len(list(queens(8))))
輸出
0
2
92
八皇后有92中放法。我們用圖形打印出其中一種看看,
def prettyprint(s):
def line(pos,length=len(s)):
return '. ' *(pos) + 'X ' +'. ' * (length-pos-1)
for pos in s:
print(line(pos))
import random
prettyprint(random.choice(list(queens(8))))
92種中的一種圖形如下,
. . . X . . . .
. X . . . . . .
. . . . . . X .
. . X . . . . .
. . . . . X . .
. . . . . . . X
X . . . . . . .
. . . . X . . .
工程檔案下載:https://download.csdn.net/download/yysyangyangyangshan/10833745