深度優先遍歷配合回溯,是解決不少問題的好方法,好比八皇后問題。 python
皇后的排布規則:n個皇后放在n*n的矩陣裏,要求一列只有一個,一行只有一個,任一斜線上只有一個(/和\)。 算法
一般,咱們會把皇后做爲一個數組,行號做爲數組的下標,而列號是數組元素的值,由此,二維平面的排布問題就成了一維數組的求解,配合檢驗函數以及回溯,就能夠求解了。 數組
這裏,我使用一個狀態表(一個好的狀態表能夠比檢驗函數要有效率的多)來維護某個行可能的填入皇后的列號,每次放下皇后或者拿起皇后,都會對狀態表進行更新。按行號的順序依次安置皇后,安放完最後一個皇后,就獲得了問題的一個解。 函數
可是這會帶來一個問題,那就是求解的難度會隨着皇后的數量增長,耗時會劇增,差很少是O(n^3)的程度。 優化
因此,咱們要找一個能夠更快解決問題的方法。 spa
注意,八皇后問題的一個隱含的條件是:咱們能夠在任意位置安放皇后,沒有次序的要求。可是當咱們抽象到二維數組的時候,每每會忽略這一點。如今,考慮到這個狀況,咱們有以下解決方案: scala
當咱們放下一個皇后以後,能夠從剩餘的全部行中,選擇一個候選列號最少的,進行下一步的安置。 指針
這樣作的好處時,能夠大大減弱搜索樹的規模,這種減弱越靠近根部,就越明顯。 code
所以,咱們能夠加入一個交換的函數。固然,這樣一來,咱們的狀態表裏就應該加上關於行號和列號的記錄了。 排序
最終代碼以下:
num=8 class Queen(object): def __init__(self,n): self.lct= n self.prs=-1 self.cdt=[1 for i in range(num)] def Count(q): s=0 for i in range(num): if q.cdt[i]>0:s+=1 return s def FindIt(q): u=q.prs+1; while u<num and q.cdt[u]<=0:u+=1 if u<num: q.prs=u return True return False def Settle(q,n): x=q[n].lct y=q[n].prs for i in range(n+1,num,1): p=q[i].cdt p[y]-=1 a=q[i].lct-x b=y-a if b>=0 and b<num:p[b]-=1 b=y+a if b>=0 and b<num:p[b]-=1 def Pickup(q,n): x=q[n].lct y=q[n].prs for i in range(n+1,num,1): p=q[i].cdt p[y]+=1 a=q[i].lct-x b=y-a if b>=0 and b<num:p[b]+=1 b=y+a if b>=0 and b<num:p[b]+=1 def Select(q,n): j,k=0,num+1 for i in range(n,num): t=Count(q[i]) if k>t:j,k=i,t if j!=n:q[n],q[j]=q[j],q[n] def ShowIt(q): for i in range(num): for j in range(num): if q[j].lct==i: for k in range(num): if q[j].prs==k: print '*', else: print '-', print '' print '' def Locate1(): q=[Queen(i) for i in range(num)] i=0 j=0 while 1: if q[i].prs<0: Select(q,i) else: Pickup(q,i) if FindIt(q[i]): if i<num-1: Settle(q,i) i+=1 else: j+=1 yield j #ShowIt(q) else: q[i].prs=-1 i-=1 if i<0:break def Locate2(): q=[Queen(i) for i in range(num)] i=0 j=0 while 1: if q[i].prs>=0:Pickup(q,i) if FindIt(q[i]): if i<num-1: Settle(q,i) i+=1 else: j+=1 yield j #ShowIt(q) else: q[i].prs=-1 i-=1 if i<0:break if __name__=='__main__': q=[Queen(i) for i in range(num)] import time t=time.time() Locate1().next() print 'once cost %.6f'%(time.time()-t) print '-----------------' t=time.time() Locate2().next() print 'once cost %.6f'%(time.time()-t)算法很簡單,也沒有註釋。我是用一個列表同時保存了皇后的行號、列號和狀態表。當皇后放下後,狀態表表示她放下時的全部可能位置(也就是放下後就不更新了),未放下的皇后的狀態表會不斷更新。一個指針,該指針左邊都是放下的,右邊都是未放下的,指針所指的皇后元素,是要進行挪動的那個。
那麼,結果呢?以下:
(08) 92 , 0.027 <=> 0.024 , ----- <=> ------ (09) 352 , 0.113 <=> 0.096 , ----- <=> ------ (10) 724 , 0.467 <=> 0.432 , ----- <=> ------ (11) 2680 , 1.919 <=> 1.956 , ----- <=> ------ (12) 14200 , 9.786 <=> 10.647 , ----- <=> ------ (13) 73712 , 52.080 <=> 59.131 , ----- <=> ------ (14) 365596 , 326.946 <=> 462.586 , ----- <=> ------ (15) ------ , ------- <=> ------- , ----- <=> ------ (16) ------ , ------- <=> ------- , 0.002 <=> 0.175 (17) ------ , ------- <=> ------- , 0.002 <=> 0.103 (18) ------ , ------- <=> ------- , 0.003 <=> 0.770 (19) ------ , ------- <=> ------- , 0.002 <=> 0.053 (20) ------ , ------- <=> ------- , 0.005 <=> 4.075 (21) ------ , ------- <=> ------- , 0.004 <=> 0.195 (22) ------ , ------- <=> ------- , 0.002 <=> 36.618 (23) ------ , ------- <=> ------- , 0.003 <=> 0.563 (24) ------ , ------- <=> ------- , 0.003 <=> 9.280 (25) ------ , ------- <=> ------- , 0.013 <=> 1.157 (26) ------ , ------- <=> ------- , 0.022 <=> 9.391 (27) ------ , ------- <=> ------- , 0.008 <=> 11.390 (28) ------ , ------- <=> ------- , 0.003 <=> 74.833 (29) ------ , ------- <=> ------- , 0.029 <=> 42.061 (30) ------ , ------- <=> ------- , 0.018 <=> ------ (40) ------ , ------- <=> ------- , 0.158 <=> ------ (50) ------ , ------- <=> ------- , 0.040 <=> ------ (60) ------ , ------- <=> ------- , 0.032 <=> ------ (70) ------ , ------- <=> ------- , 0.077 <=> ------ (80) ------ , ------- <=> ------- , 0.054 <=> ------ (90) ------ , ------- <=> ------- , 2.162 <=> ------
上表的最左邊是皇后的個數;第一欄是解的數量;隨後的一對數據依次是優化和非優化版本的求所有解的耗時;最後一對數據依次是優化和非優化版本的求第一個解的耗時。
可見這種優化的效果仍是很好的。(雖然小數量是反而慢一些,但這是必然的,算法越精巧,也就要作越多的處理操做,天然會在處理小規模數據時不利)
這種改進,對於沒有次序要求的深度搜索/回溯求解很是有效,好比,數獨。
本質上,這種方法和A*算法有些殊途同歸,一個是旨在「剪枝」,一個則使用經驗函數「抄近路」,而它們的使用上,其實都是同樣的:對全部可能的下一步進行排序以後,選擇效果最好/概率最高的進行。