用遺傳算法做數獨求解器

轉載請帶源地址,謝謝:http://hi.baidu.com/2427/blog/item/aa24643813d6622c97ddd8d1.html

用遺傳算法做數獨求解器

 

I 閒話在前

我不知道有多少人曾經試過這種方法,但我在網上沒有找到過類似的內容。

剛開始接觸遺傳算法的時候,根本不知道他到底有什麼用,只知道是個很好玩的算法。於是乎,第一時間想到了拿這東西解數獨,但經過N次嘗試失敗後,放棄了。

前幾天羣裏有人偶爾又提到遺傳算法,於是便又想起了之前的那個數獨問題,並再次嘗試用新方法解決問題。

 

II 解決方案

首先提一下之前失敗的例子。

在我想到的第一種解決方案中首先想到了是把要填的數提出來,做成一個隨機序列。拿下面這個數獨爲例:

 

 


在這裏例子中,從左到右、自上到下依次缺少的數字爲:

然後把這些數字隨機生成一個字串,位置不做控制。

第二種方式我纔去了二進制方式,首先查看數獨中有多少個空位,然後生成NN爲空位數量)組八位二進制數據,對數據進行拼接,所以在我第二種方法中看到的是下面這樣的數據。

每組二進制數據轉換爲整形後得到的是0-255之間的數字,然後乘以係數0.035156259/256得到)加1就會得到1-9之間任意數。

這兩種效果都不是很理想,第一種方式也許有萬分之一的機率能算出正確結果(因爲我在運行的時候確實算出過一次),正常情況下很看出結果。第二種出結果的機率更小,因爲在第二種結構中,存在的一個致命弊端就是錯誤數據。

所以我放棄了這兩種方式,第三種方式中,我採用了和第一種方式類似的結構形式,以塊爲單位,計算出每塊中缺失數字,然後以組的方式拼串,得到如下數據(塊從上到下、從左到右的方式排列,因爲在我計算塊的時候方向搞錯了,也懶得改,就這麼些了,不影響計算):

下面我要介紹的就是第三種排列方式,所以正文從這裏開始!

 

1.數獨

在這裏,還是有必要介紹一下數獨,不過本節可以跳過,因爲我也是複製百度百科的,哈哈!

九宮格數獨,是一種源自18世紀末的瑞士,後在美國發展、並在日本得以發揚光大的數字謎題。數獨盤面是個九宮,每一宮又分爲九個小格。在這八十一格中給出一定的已知數字和解題條件,利用邏輯和推理,在其他的空格上填入1-9的數字。使1-9每個數字在每一行、每一列和每一宮中都只出現一次。這種遊戲全面考驗做題者觀察能力和推理能力,雖然玩法簡單,但數字排列方式卻千變萬化,所以不少教育者認爲數獨是訓練頭腦的絕佳方式。

數獨遊戲我玩過很多,最囂張的一次用了不到三分鐘解決簡單級別的遊戲,不過我所知道最牛的人只用一份二十秒就搞定的。如果不考慮遺傳算法,用暴力破解法(也就是遍歷),最困看的數獨計算都是不需要話費時間的,我以爲朋友爲了證明效率,專門寫了一個遍歷的方法,非常簡單,但是非常有效,我想網上的求解器大多也是用這種方式吧。

2.問題轉化

數獨橫向、縱向、小塊中只允許填寫1-9之間不重複的數字,首先我們只需要確定出一種數據來即可(橫向、縱向或是小塊),這是非常簡單的,然後我們通過調整小塊中的數據,來得到其他兩種數據。

3.編碼方式

上面已經提到過我這次用到的編碼方式,以小塊爲單位(就是我先計算出來的那種數據)進行數據隨機排列,這種數據格式的要求就是首先要保證組與組的順序不能亂,再次要保證每組中的數據只能是位置條換,不能改成其他數字。這就有別於第一種編碼方式了,因爲在第一種編碼方式中數據是不分組的,也就意味着可能有九個9同時出現在第一塊中,效率太低。

4.羣體

我習慣叫這個東西部落,可能是因爲玩《孢子》太多的緣故吧。一個部落中存在多個個體,數量上我記得有個前輩說過,部落的大小取決於基因的長短,一般部落大小是基因的4-5倍合適,但是我經試驗,發現我的算法一般羣體小的話收斂太慢,但太大的話計算起來又太慢,所以做到10-20倍就差不多了,在看神經網絡的時候看到過這樣一句話:你只能把任何建議當作不可全信的東西,主要還要靠自己的不斷嘗試和失敗中獲得經驗。

羣體初始化完全是隨機的,但是要遵循編碼方式的兩個原則:1.分組順序不能亂,2.組中單個數據不能改。

5.選擇與交叉

選擇上我用的比較多的還是輪盤賭,這個我就不細說了,不知道的可以百度百科看一下什麼叫輪盤賭,或者重新學習一下遺傳算法。

首先在染色體中隨機選擇兩個位置,然後把父代染色體進行按位置交叉運算,如下圖:

紅色位要交叉位(開始位爲3,結束位爲13,這裏由於空間原因只寫部分基因)

交叉後得到

我們看到,在交叉後第一組數據和最後一組數據中產生了錯誤信息,就是重複位,所以,我們下一步要做的就是把錯誤的基因更正過來,做法如下:

我們首先查看未交叉部分的基因與交叉部分那些基因產生了重複,在我們這個例子中,個體C中的2產生了重複,而交叉部分中,2對應的基因是5,那麼我們就把未交叉部分的2改成5,於是得到:

(我儘量把這部分說細寫,因爲肯能會有些繞)

更正後又發現,5產生了重複,而交叉部分的5對應的基因是9,那麼我們再次把5(第二位)換成9,於是得到:

更正有發現9仍然重複,交叉部分的9對應的基因是6,那麼再次把9換成6,得到如下個體

到此爲止,第一組基因的錯誤被更正,但是在最後一組中仍然有錯誤基因,在個體D中也存在錯誤基因,我們再依次更正,得到修正後的個體如下:





中間所有基因都參與交換的組我們不用管,因爲其中不會產生錯誤數據。

一般交叉率會被設置在70%左右,還是那句話,試驗纔是正道,自己做不同的交叉率試試看,哪種適合自己用哪種。

(這部分講的有些囉嗦,如果看不懂的可以Mail[email protected],或是去我的博客提問,http://hi.baidu.com/2427

6.變異

變異操作十分簡單,只要隨機選中一組數字,然後再這個組中隨機選擇兩位進行交換就行了,以下是操作方式

選中變異組爲第一組,變異位分別是第二位和第五位,變異後得到

一般變異率在1%0.1%之間,自己做試驗看吧。

變異是爲了方式部落陷入局部最優中,這樣到死都算不出結果了。羣裏有位朋友說過,有一種機制可以防止局部最優,就是在每次繁殖後都加入一些新的隨機個體,但是我感覺這樣會違反遺傳算法的一些理論,沒有采納,不過在局部最優產生的時候(或是個體同質化嚴重的時候)採用這個方法還是不錯的。

7.計算

這個我放到邏輯的最後來講了,其實他和選擇是息息相關的,計算方法的憂慮決定了算法的收斂速度,我的算法可能不是最好的,但是我可以爲初學者提供一種不錯的解決方式。

在輪盤賭中,被選中的機率取決於個體所佔區域的大小,而區域的大小是根據個體得分決定的,所以計算得分成了關鍵。在本例中,我用了減分法進行計算,總分爲144分(一會介紹爲什麼是144分),然後計算每行和每列中出現過多少重複,每出現一次重複減一分。所以這裏就看出了我的144分是怎麼計算的,每行或每列最多出現重複的數量是8,所以單、列減分就是8,8*18=144分。當然,在我提出這個算法的時候有個朋友告訴我說64分就可以了,因爲不可能出現所有空格中都出現一個數字的情況。不過這種方法我沒細考慮呢,或許他是對的,各位看官驗證吧。考慮到這種比分在論盤中體現出來的差習性會很小,所以可以把得分做平方處理,這樣會出現指數型變化,讓小的越小,大的越大,也許這種方式是不科學的,但是值得去嘗試。另外囉嗦一句,我再剛開始做的時候想到了把得分乘3,但後來改正了,因爲這中計算方式並不能讓區域產生變化。

 

8.進化

一切準備就緒了,開始進化吧,下面是我這種方法的進化圖,分別體現出了最優個體、最劣個體和平均得分。

 

 


圖中的進化在495代(這個代不準確,懶得改了)算出真確結果,一般情況下在一千代之內才能算出接過來,效率非常低,因爲在最開始的時候我忘了一件事,把上一代的最優個體帶入,這是很關鍵的,通過下面這個圖你就能比較出來了。

 

 


看到所有線幾乎都是直線飆升上去的,一般在一百代之內就能算出結果來,這個截圖線飈的還不是太好,我一次用了32代就做出來了,那條線才叫漂亮。

我最優個體選擇了5%,這個比率自己做試驗看一下,帶的太多了容易讓結果跑到局部最優,或者是收斂太慢,太少了達不到效果。

 

結束語

在我這個算法中也存在一些沒有解決的問題,就是效率,有時在運算的時候線可能會飈直了,這就是局部最優產生了,有一次我計算的時候竟然三個線飈到了一起了,這是非常致命的,期待高手對算法進行優化。

就囉嗦到這吧,文采太差勁了,不適合寫教程,有不明白的或是想討論一下的mail我吧([email protected])。

我的源程序放在了CSDN的下載頻道,叫《用遺傳算法做數獨求解器》,需要的可以下載。

http://download.csdn.net/source/2805016

 

到此爲止,期待高手改正算法和文章中的錯字,謝謝您的耐心!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章