八數碼問題-8重境界

八數碼的八境界

  研究經典問題,空說不好,我們拿出一個實際的題目來演繹。八數碼問題在北大在線測評系統中有一個對應的題,題目描述如下:


Eight

Time Limit: 1000MS    Memory Limit: 65536K  Special Judge


Description                                 


The 15-puzzle has been aroundfor over 100 years; even if you don't know it by that name, you've seen it. Itis constructed with 15 sliding tiles, each with a number from 1 to 15 on it,and all packed into a 4 by 4 frame with one tile missing. Let's call themissing tile 'x'; the object of the puzzle is to arrange the tiles so that theyare ordered as:


 1 2  3  4


 5 6  7  8


 9  10 1112


 13 14 15 x


where the only legal operationis to exchange 'x' with one of the tiles with which it shares an edge. As anexample, the following sequence of moves solves a slightly scrambled puzzle:


 1 2  3  4    1  2 3  4    1 2  3  4   1  2  3  4


 5 6  7  8   5  6  7 8    5  6 7  8    5 6  7  8


 9  x 1012    9 10  x 12   9 10 11 12    9 10 11 12


13 14 11 15   13 14 11 15  13 14  x 15   13 14 15 x


           r->           d->           r->


The letters in the previousrow indicate which neighbor of the 'x' tile is swapped with the 'x' tile ateach step; legal values are 'r','l','u' and 'd', for right, left, up, and down,respectively.


Not all puzzles can be solved;in 1870, a man named Sam Loyd was famous for distributing an unsolvable versionof the puzzle, and


frustrating many people. Infact, all you have to do to make a regular puzzle into an unsolvable one is toswap two tiles (not counting the missing 'x' tile, of course).


In this problem, you willwrite a program for solving the less well-known 8-puzzle, composed of tiles ona three by three arrangement.


Input


You will receive a descriptionof a configuration of the 8 puzzle. The description is just a list of the tilesin their initial positions, with the rows listed from top to bottom, and thetiles listed from left to right within a row, where the tiles are representedby numbers 1 to 8, plus 'x'. For example, this puzzle


 1 2  3


 x 4  6


 7 5  8


is described by this list:


 1 2 3 x 4 6 7 5 8


Output


You will print to standardoutput either the word ``unsolvable'', if the puzzle has no solution, or astring consisting entirely of the letters 'r', 'l', 'u' and 'd' that describesa series of moves that produce a solution. The string should include no spacesand start at the beginning of the line.


Sample Input


 2 3  4  1 5  x  7 6  8


Sample Output


ullddrurdllurdruldr


 


  這個題目是SpecialJudge,任意找出一組移法就行,但是很多時候,我們需要找到步數最少的移法,所以,這裏以步數最少的移法爲目的。真正優化這個問題涉及到很多,比如A*、全排列哈希、堆優化等。一境界一代碼,咱們一個境界一個境界走,慢慢優化這個經典問題,當然,我不是那麼無聊,不會把所有境界都列出代碼……


 


  境界一、 暴力廣搜+STL


  開始的時候,自然考慮用最直觀的廣搜,因爲狀態最多不超過40萬,計算機還是可以接受的,由於廣搜需要記錄狀態,並且需要判重,所以可以每次圖的狀態轉換爲一個字符串,然後存儲在stl中的容器set中,通過set的特殊功能進行判重,由於set的內部實現是紅黑樹,每次插入或者查找的複雜度爲Log(n),所以,如果整個算法遍歷了所有狀態,所需要的複雜度爲n*Log(n),在百萬左右,可以被計算機接受,由於對string操作比較費時,加上stl全面性導致 速度不夠快,所以計算比較費時,這樣的代碼只能保證在10秒內解決任何問題。但,明顯效率不夠高。POJ上要求是1秒,無法通過。


 


  境界二、廣搜+哈希


  考慮到費時主要在STL,對於大規模的遍歷,用到了ST的set和string,在效率上的損失是很大的,因此,現在面臨一個嚴重的問題,必須自己判重,爲了效率,自然是自己做hash。有點麻煩,hash函數不好想,實際上是9!種排列,需要每種排列對應一個數字。網上搜索,得知了排列和數字的對應關係。取n!爲基數,狀態第n位的逆序值爲哈希值第n位數。對於空格,取其爲9,再乘以8!。例 如,1 3 7 24 6 9 5 8 的哈希值等於:0*0! + 2*1! + 0*2! + 1*3! + 3*4! +1*5! + 0*6! + 1*7! + 0*8! <9!具體的原因可以去查查一些數學書,其中1 2 34 5 6 7 8 9 的哈希值是0 最小,9 8 7 6 54 3 2 1 的哈希值是(9!-1)最大。而其他值都在0 到(9!-1) 中,且均唯一。然後去掉一切STL之後,甚至包括String之後,得到單向廣搜+Hash的代碼,算法已經可以在三秒鐘解決問題,可是還是不夠快!POJ時限是1秒,後來做了簡單的更改,將路徑記錄方法由字符串改爲單個字符,並記錄父節點,得到解,這次提交,266ms是解決單問題的上限。當然,還有一個修改的小技巧,就是逆序對數不會改變,通過這個,可以直接判斷某輸入是否有可行解。由於對於單組最壞情況的輸入,此種優化不會起作用,所以不會減少單組輸入的時間上限。


 


  境界三、廣搜+哈希+打表


  好,問題可以在200—300ms間解決,可是,這裏我們注 意到一個問題,最壞情況下,可能搜索了所有可達狀態,都無法找到解。如果這個題目有多次輸入的話,每次都需要大量的計算。其實,這裏可以從反方向考慮下,從最終需要的狀態,比如是POJ 1077需要的那種情況,反着走,可達的情況是固定的。可以用上面說的那種相應的Hash的方法,找到所有可達狀態對應的值,用一個bool型的表,將可達狀態的相應值打表記錄,用“境界三”相似的方法記錄路徑,打入表中。然後,一次打表結束後,每次輸入,直接調用結果!這樣,無論輸入多少種情況,一次成功,後面在O(1)的時間中就能得到結果!這樣,對於ZOJ的多組輸入,有致命的幫助!


 


  境界四、雙向廣搜+哈希
  Hash,不再贅述,現在,我們來進行進一步的優化,爲了減少狀態的膨脹,自然而然的想到了雙向廣搜,從輸入狀態點和目標狀態1 2 3 4 5 6 7 8 9同時開始搜索,當某方向遇到另一個方向搜索過的狀態的時候,則搜索成功,兩個方向對接,得到最後結果,如果某方向遍歷徹底,仍然沒有碰上另一方向,則無法完成。


 


  境界五、A*+哈希+簡單估價函數
  用到廣搜,就可以想到能用經典的A*解決,用深度作爲g(n),剩下的自然是啓發函數了。對於八數碼,啓發函數可以用兩種狀態不同數字的數目。接下來就是A*的套路,A*的具體思想不再贅述,因爲人工智能課本肯定比我講的清楚。但是必須得注意到,A*需要滿足兩個條件:


1.h(n)>h'(n),h'(n)爲從當前節點到目標點的實際的最優代價值。


2.每次擴展的節點的f值大於等於父節點的f值小。


自然,我們得驗證下我們的啓發函數,h驗證比較簡單不用說,由於g是深度,每次都會較父節點增1。再看h,認識上, 我們只需要將h看成真正的“八數碼”,將空格看空。這裏,就會發現,每移動一次,最多使得一個數字迴歸,或者說不在位減一個。 h最多減小1,而g認爲是深度,每次會增加1。所以,f=g+h, 自然非遞減,這樣,滿足了A*的兩個條件,可以用A*了!




  境界六、A*+哈希+曼哈頓距離


  A*的核心在啓發函數上,境界五若想再提升,先想到的是啓發函數。這裏,曼哈頓距離可以用來作爲我們的啓發函數。曼哈頓距離聽起來神神祕祕,其實不過是“絕對軸距總和”,用到八數碼上,相當與將所有數字歸位需要的最少移動次數總和。作爲啓發函數,自然需要滿足“境界五”提到的那兩個條件。現在來看這個曼哈頓距離,第一個條件自然滿足。對於第二個,因爲空格被我們剝離出去,所以交換的時候只關心交換的那個數字,它至多向目標前進1,而深度作爲g每次是增加1的,這樣g+h至少和原來相等,那麼,第二個條件也滿足了。A*可用了,而且,有了個更加優化的啓發函數。


 


  境界七、A*+哈希+曼哈頓距離+小頂堆


  經過上面優化後,我們發現了A*也有些雞肋的地方,因爲需要每次找到所謂Open表中f最小的元素,如果每次排序,那麼排序的工作量可以說是很大的,即使是快排,程序也不夠快!這裏,可以想到,由於需要動態的添加元素,動態的得到程序的最小值,我們可以維護一個小頂堆,這樣的效果就是。每次取最小元素的時候,不是用一個n*Log(n)的排序,而是用log(n)的查找和調整堆,好,算法又向前邁進了一大步。


 


  境界八、IDA*+曼哈頓距離


  IDA*即迭代加深的A*搜索,實現代碼是最簡練的,無須狀態判重,無需估價排序。那麼就用不到哈希表,堆上也不必應用,空間需求變的超級少。效率上,應用了曼哈頓距離。同時可以根據深度和h值,在找最優解的時候,對超過目前最優解的地方進行剪枝,這可以導致搜索深度的急劇減少,所以,這,是一個致命的剪枝!因此,IDA*大部分時候比A*還要快,可以說是A*的一個優化版本!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章