八皇后問題是十九世紀著名的數學家高斯1850年提出 。如下爲python語言的八皇后代碼,摘自《Python基礎教程》,代碼相對於其餘語言,來得短小且一次性能夠打印出92種結果。同時能夠擴展爲九皇后,十皇后問題。python
問題:在一個8*8
棋盤上,每一行放置一個皇后旗子,且它們不衝突。衝突定義:同一列不能有兩個皇后,每個對角線也不能有兩個皇后。固然,三個皇后也是不行的,四個也是不行的,憑你的智商應該能夠理解吧。算法
解決方案:回溯與遞歸。數據結構
介紹:dom
1.回溯法函數
回溯法是一種選優搜索法,按選優條件向前搜索,以達到目標。當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步從新選擇,這種走不通就退回再走的技術爲回溯法,而知足回溯條件的某個狀態的點稱爲「回溯點」。參見百度百科優化
2.遞歸法編碼
階乘 n! = 1 x 2 x 3 x ... x n
翻譯
用函數fact(n)
表示,能夠看出:code
fact(1) = 1 fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n
因而,fact(n)
用遞歸的方式寫出來就是:htm
def fact(n): if n==1: return 1 return n * fact(n - 1)
若是計算fact(5)
,結果以下:
===> fact(5) ===> 5 * fact(4) ===> 5 * (4 * fact(3)) ===> 5 * (4 * (3 * fact(2))) ===> 5 * (4 * (3 * (2 * fact(1)))) ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120
使用遞歸函數須要注意防止棧溢出。在計算機中,函數調用是經過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。因爲棧的大小不是無限的,因此,遞歸調用的次數過多,會致使棧溢出。能夠試試fact(1000):
>>> fact(1000) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in fact ... File "<stdin>", line 4, in fact RuntimeError: maximum recursion depth exceeded
解決遞歸調用棧溢出的方法是經過尾遞歸優化。
尾遞歸是指,在函數返回的時候,調用自身自己,而且,return語句不能包含表達式。這樣,編譯器或者解釋器就能夠把尾遞歸作優化,使遞歸自己不管調用多少次,都只佔用一個棧幀,不會出現棧溢出的狀況。如:
def factorial(n, acc=1): if n == 0: return acc return factorial(n-1, n*acc)
函數返回時只調用了它自己factorial(n-1, n*acc)
問題是Python標準的解釋器沒有針對尾遞歸作優化,任何遞歸函數都存在棧溢出的問題。
python源碼:
# -*- coding: utf-8 -*- #python默認爲ascii編碼,中文編碼能夠用utf-8 import random #隨機模塊 def conflict(state,col): #衝突函數,row爲行,col爲列 row=len(state) for i in range(row): if abs(state[i]-col) in (0,row-i):#重要語句 return True return False def queens(num=8,state=()): #生成器函數 for pos in range(num): if not conflict(state, pos): if len(state)==num-1: yield(pos,) else: for result in queens(num, state+(pos,)): yield (pos,)+result def queenprint(solution): #打印函數 def line(pos,length=len(solution)): return '. '*(pos)+'X '+'. '*(length-pos-1) for pos in solution: print line(pos) for solution in list(queens(8)): print solution print ' total number is '+str(len(list(queens()))) print ' one of the range is:\n' queenprint(random.choice(list(queens())))
結果:
(0, 4, 7, 5, 2, 6, 1, 3) (0, 5, 7, 2, 6, 3, 1, 4) (0, 6, 3, 5, 7, 1, 4, 2) (0, 6, 4, 7, 1, 3, 5, 2) (1, 3, 5, 7, 2, 0, 6, 4) (1, 4, 6, 0, 2, 7, 5, 3) (1, 4, 6, 3, 0, 7, 5, 2) (1, 5, 0, 6, 3, 7, 2, 4) (1, 5, 7, 2, 0, 3, 6, 4) (1, 6, 2, 5, 7, 4, 0, 3) (1, 6, 4, 7, 0, 3, 5, 2) (1, 7, 5, 0, 2, 4, 6, 3) (2, 0, 6, 4, 7, 1, 3, 5) (2, 4, 1, 7, 0, 6, 3, 5) (2, 4, 1, 7, 5, 3, 6, 0) (2, 4, 6, 0, 3, 1, 7, 5) (2, 4, 7, 3, 0, 6, 1, 5) (2, 5, 1, 4, 7, 0, 6, 3) (2, 5, 1, 6, 0, 3, 7, 4) (2, 5, 1, 6, 4, 0, 7, 3) (2, 5, 3, 0, 7, 4, 6, 1) (2, 5, 3, 1, 7, 4, 6, 0) (2, 5, 7, 0, 3, 6, 4, 1) (2, 5, 7, 0, 4, 6, 1, 3) (2, 5, 7, 1, 3, 0, 6, 4) (2, 6, 1, 7, 4, 0, 3, 5) (2, 6, 1, 7, 5, 3, 0, 4) (2, 7, 3, 6, 0, 5, 1, 4) (3, 0, 4, 7, 1, 6, 2, 5) (3, 0, 4, 7, 5, 2, 6, 1) (3, 1, 4, 7, 5, 0, 2, 6) (3, 1, 6, 2, 5, 7, 0, 4) (3, 1, 6, 2, 5, 7, 4, 0) (3, 1, 6, 4, 0, 7, 5, 2) (3, 1, 7, 4, 6, 0, 2, 5) (3, 1, 7, 5, 0, 2, 4, 6) (3, 5, 0, 4, 1, 7, 2, 6) (3, 5, 7, 1, 6, 0, 2, 4) (3, 5, 7, 2, 0, 6, 4, 1) (3, 6, 0, 7, 4, 1, 5, 2) (3, 6, 2, 7, 1, 4, 0, 5) (3, 6, 4, 1, 5, 0, 2, 7) (3, 6, 4, 2, 0, 5, 7, 1) (3, 7, 0, 2, 5, 1, 6, 4) (3, 7, 0, 4, 6, 1, 5, 2) (3, 7, 4, 2, 0, 6, 1, 5) (4, 0, 3, 5, 7, 1, 6, 2) (4, 0, 7, 3, 1, 6, 2, 5) (4, 0, 7, 5, 2, 6, 1, 3) (4, 1, 3, 5, 7, 2, 0, 6) (4, 1, 3, 6, 2, 7, 5, 0) (4, 1, 5, 0, 6, 3, 7, 2) (4, 1, 7, 0, 3, 6, 2, 5) (4, 2, 0, 5, 7, 1, 3, 6) (4, 2, 0, 6, 1, 7, 5, 3) (4, 2, 7, 3, 6, 0, 5, 1) (4, 6, 0, 2, 7, 5, 3, 1) (4, 6, 0, 3, 1, 7, 5, 2) (4, 6, 1, 3, 7, 0, 2, 5) (4, 6, 1, 5, 2, 0, 3, 7) (4, 6, 1, 5, 2, 0, 7, 3) (4, 6, 3, 0, 2, 7, 5, 1) (4, 7, 3, 0, 2, 5, 1, 6) (4, 7, 3, 0, 6, 1, 5, 2) (5, 0, 4, 1, 7, 2, 6, 3) (5, 1, 6, 0, 2, 4, 7, 3) (5, 1, 6, 0, 3, 7, 4, 2) (5, 2, 0, 6, 4, 7, 1, 3) (5, 2, 0, 7, 3, 1, 6, 4) (5, 2, 0, 7, 4, 1, 3, 6) (5, 2, 4, 6, 0, 3, 1, 7) (5, 2, 4, 7, 0, 3, 1, 6) (5, 2, 6, 1, 3, 7, 0, 4) (5, 2, 6, 1, 7, 4, 0, 3) (5, 2, 6, 3, 0, 7, 1, 4) (5, 3, 0, 4, 7, 1, 6, 2) (5, 3, 1, 7, 4, 6, 0, 2) (5, 3, 6, 0, 2, 4, 1, 7) (5, 3, 6, 0, 7, 1, 4, 2) (5, 7, 1, 3, 0, 6, 4, 2) (6, 0, 2, 7, 5, 3, 1, 4) (6, 1, 3, 0, 7, 4, 2, 5) (6, 1, 5, 2, 0, 3, 7, 4) (6, 2, 0, 5, 7, 4, 1, 3) (6, 2, 7, 1, 4, 0, 5, 3) (6, 3, 1, 4, 7, 0, 2, 5) (6, 3, 1, 7, 5, 0, 2, 4) (6, 4, 2, 0, 5, 7, 1, 3) (7, 1, 3, 0, 6, 4, 2, 5) (7, 1, 4, 2, 0, 6, 3, 5) (7, 2, 0, 5, 1, 4, 6, 3) (7, 3, 0, 2, 5, 1, 6, 4) total number is 92 one of the range is: X . . . . . . . . . . . . . X . . . . X . . . . . . . . . X . . . . . . . . . X . X . . . . . . . . . . X . . . . . X . . . . .
源碼解析:
主要利用衝突函數檢測衝突,若是衝突則回溯,遞歸用到python的yield語句,該語句涉及python的生成器。
衝突函數:
def conflict(state,col): #衝突函數,row爲行,col爲列 row=len(state) for i in range(row): if abs(state[i]-col) in (0,row-i):#重要語句 return True return False
state爲皇后的狀態,類型是一個元組,如(7, 3, 0, 2, 5, 1, 6, 4)
,元組是不可變對象,一經建立不能修改,元組是建立生成器的一種方法。
步驟:
假設第一行到第三行的皇后都沒衝突,這個時候要檢測第四行皇后是否衝突。如第一行皇后在第五列,第二行皇后在第八列,第三行皇后在第四列,檢驗第四行皇后放在哪一列不會衝突。
. . . . X . . . . . . . . . . X . . . X . . . .
這時state=(4,7,3),col=?
1.得出目前沒衝突行數row
row=len(state)
2.從1~row行依次檢測是否與row+1行皇后衝突
for i in range(row):
3.若是row+1行皇后所在的列col與其餘行皇后的列相同或處於對角線,則衝突
if abs(state[i]-col) in (0,row-i):#重要語句 return True
以上語句翻譯爲(其餘行所在的列-要求檢測所在行的列)相差範圍爲0~row-i
則衝突。
傻瓜式教學:
第一行與第四行衝突,要麼在同一列,要麼在對角線,當對角線時列數相差3(由於第一行與第二行對角線相差1,第二行與第三行對角線相差1,則第一行與第三行對角線相差2,以此類推,第一行與第四行衝突,則相差3)
當第四行所在列col=4,這時abs ( state[0]-4 ) in (0 , 3-0)
爲真,由於4-4=0
,如:
. . . . X . . . . . . . . . . X . . . X . . . . . . . . X . . . 同列衝突
當第四行所在列col=7,這時abs ( state[0]-7 ) in (0 , 3-0)
爲真,由於abs (4-7)=3
,如:
. . . . X . . . . . . . . . . X . . . X . . . . . . . . . . . X 對角線衝突
大家這麼聰明,該重要語句應該懂吧。
生成器函數:
def queens(num=8,state=()): #生成器函數 for pos in range(num): if not conflict(state, pos): if len(state)==num-1: yield(pos,) else: for result in queens(num, state+(pos,)): yield (pos,)+result
生成器:
經過列表生成式,咱們能夠直接建立一個列表。可是,受到內存限制,列表容量確定是有限的。並且,建立一個包含100萬個元素的列表,不只佔用很大的存儲空間,若是咱們僅僅須要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。因此,若是列表元素能夠按照某種算法推算出來,那咱們是否能夠在循環的過程當中不斷推算出後續的元素呢?這樣就沒必要建立完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱爲生成器(Generator)。
參考:生成器
步驟:
1.下面該語句爲構建全部皇后擺放狀況打下基礎。能夠嘗試全部狀況。
for pos in range(num):
2.若是不衝突,則遞歸構造棋盤。
if not conflict(state, pos):
3.若是棋盤狀態state已經等於num-1
,即到達倒數第二行,而這時最後一行皇后又沒衝突,直接yield
,打出其位置(pos, )
,Python在顯示只有1個元素的元組時,也會加一個逗號,,以避免你誤解成數學計算意義上的括號。
不然遞歸,打印(pos , )+ result
if len(state)==num-1: yield(pos,) else: for result in queens(num, state+(pos,)): yield (pos,)+result
傻瓜式教學:
例如pos=0
,第一行放在第一列,這時不會衝突,可是不會進入if,由於還沒到達倒數第二行,進入else後,再調用queens(num, state+(pos,)
,這時進入第二行,再次遞歸展開則是queens(num,state+(pos, )+(pos, ) )
,到達最後一行時返回(pos, )
,再返回倒數第二行,再返回倒數第三行,最後到達最開始那層(pos, )+result
, pos
爲第一行皇后所在列,result包含第二行皇后所在列和另外一個result,就是這麼複雜,但願好好琢磨。
優美格式的打印函數就不講了。
講講打印全部結果
for solution in queens(8): print solution
queens(8)由於生成器函數的for循環,每一次循環都會yield一個元組出來,因此有不少種狀況,能夠把它所有打出來。
也能夠用list包裝成列表再統計一下多少種數目。
print ' total number is '+str(len(list(queens()))
隨機優美打印一個棋盤狀況:
print ' one of the range is:\n' queenprint(random.choice(list(queens())))