吳昊品游戲核心算法 Round 17 —— 吳昊教你玩拼圖游戲(15 puzzle)


  以上,8--puzzle轉化為了15--puzzle,狀態數增加了,游戲的難度也加大了,AI也更加具有挑戰性。

  現在,我們的PUZZLE的目標狀態變成了如下的情況,游戲的規模由3*3變成了4*4,這么一變不得了,狀態數由O(9!)變成了O(16!),由於n!的增長速率在NP中都是變態的,所以,即使是從9增加到了16,規模都會大許多,這樣的話,優化的難度也大大增加了。

 

 

  這里,我們先不慌着再AI,因為,15--puzzle的AI設計將更為復雜,因為考慮到狀態變得更多,需要更多更好的優化才可以。我們不妨在考慮移動的過程AI之前,先考慮一種更為簡單的判定性的AI,也就是我們玩的這個游戲是否必定存在一個解(如果有解的話,肯定存在最優解,當然,次優解是無窮多的,你可以將空白方塊繞一圈,這樣,路徑的長度又加了4),那么,如何判定呢?

15數碼的解的存在性判定AI

  我們考慮到如下的事實,也就是:空格(0)往左或者往右則置換是不變的。如果往上或者往下,必定交換偶數個數,所以逆序數的奇偶性不變。這個很容易理解,左換和右換,15數碼的順序不會有絲毫的改變,如果是上下交換的話,對應的那兩個數和原來那兩個所在位置的數的奇偶性不會有絲毫的改變,那么兩者取並,我們可以得出,其充要條件可以描述為:只要比較比較其逆序數的奇偶性就可以了。

 

 

    Solve:

   Highlights:我們輸入一個游戲的狀態,可以看到是否可以變為目標狀態,同樣的思路可以運用於8--puzzle問題。 

 

 1  #include<iostream>
 2   using  namespace std;
 3 
 4   int cur_num[ 16]; 
 5  
 6   // 位置對應的數字 
 7    int des_num[ 16]={ 1, 2, 3, 4, 8, 7, 6, 5, 9, 10, 11, 12, 0, 15, 14, 13};
 8 
 9   bool solvable()
10  {
11     int ni1= 0,ni2= 0;
12     // 找到當前狀態的逆序對的總數
13      for( int i= 0;i< 16;i++)
14    {
15       if(cur_num[i]== 0continue;
16       for( int j=i+ 1;j< 16;j++)
17      {
18         if(cur_num[j]== 0continue;
19         if(cur_num[i]>cur_num[j])
20        {
21          ni1++;
22        }
23      }
24    }
25     for( int i= 0;i< 16;i++)
26    {
27       if(des_num[i]== 0continue;
28       for( int j=i+ 1;j< 16;j++)
29      {
30         if(des_num[j]== 0continue;
31         if(des_num[i]>des_num[j])
32        {
33          ni2++;
34        }
35      }
36    }
37     // 如果奇偶性相同的話,則說明是可以互相變化得到的
38      if(((ni1-ni2)& 1)== 0return  true;
39     return  false;
40  }
41 
42   int main()
43  {
44     int t,a;
45     // 讀入樣例的總數,a用來載入數據 
46     cin>>t;
47     while(t--)
48    {
49       // 考慮到逆序數,這里用這種方式讀數據
50        for( int i= 0;i< 4;i++)
51      {
52        cin>>a;
53        cur_num[i]=a;
54      }
55       for( int i= 7;i> 3;i--)
56      {
57        cin>>a;
58        cur_num[i]=a;
59      }
60       for( int i= 8;i< 12;i++)
61      {
62        cin>>a;
63        cur_num[i]=a;
64      }
65       for( int i= 15;i> 11;i--)
66      {
67        cin>>a;
68        cur_num[i]=a;
69      }
70       if(solvable()) cout<< " YES\n ";
71       else cout<< " NO\n ";
72    }
73     return  0;
74  }
75 
76   

 

 

 

   15數碼問題的解的存在性定理的嚴謹證明(Kobe給出的)

//這里指的是網上的那位Kobe大神,而不是NBA上面的科比

設0當前的位置為(x,y);起始位置為(4,4);

每次0移動后,定義dist=abs(x-4)+abs(y-4);

定義perm為逆序對的數目;

記s=perm+dist;

則有結論:每次0的位置移動后,s的奇偶性保持不變。

證明過程如下:

0每次移動后,不管方向如何,dist只會加1或減1,所以dist的奇偶性會改變。

要證明s的奇偶性保持不變,只要證明,perm的奇偶性改變。

對於0向左移動,開始的的序列為:

a[1],a[2].....a[i],0,a[i+2],...a[16];

移動后序列為:

a[1],a[2].....0,a[i],a[i+2],...a[16];

很明顯,perm減1,奇偶性改變。

對於0向右移動同理可證perm奇偶性改變。

對於0向下移動,

開始的序列為:

a[1],a[2]...0,a[i],a[i+1],a[i+2],a[i+3]...a[16];

移動后序列為:

a[1],a[2]...a[i+3],a[i],a[i+1],a[i+2],0...a[16];

只要考慮

{0,a[i],a[i+1],a[i+2],a[i+3]}

{a[i+3],a[i],a[i+1],a[i+2],0}

的逆序對之差即可,很明顯,

對於包含0的逆序對數目改變為4。

對於a[i],a[i+1],a[i+2],改變為1,

4+1+1+1=7為奇數。

所以結論成立。

 

  15數碼問題的解的過程性闡述AI —— 這里用四種方法來闡述

Highlights: 太多了,有些還利用了論文中的相關觀點,BFS,DFS比較一般,后面兩種(A*和IDA*用到了啟發式搜索,引入了一些主觀性),可以說,亮點很多,比如閉合集和開環集,利用這種方式免去了8--puzzle問題中的用數組來標記訪問節點的辦法,這樣可以避免了嚴重的超內存

 

  1 #include <iostream>
  2 
  3  #include <cstring>
  4 
  5  #include <limits>
  6 
  7  #include <vector>
  8 
  9  #include <queue>
 10 
 11  #include <stack>
 12 
 13  #include < set>
 14 
 15  #include <map>
 16 
 17         
 18 
 19   using  namespace std;
 20 
 21  
 22 
 23   // 開辟一個4*4的布局        
 24 
 25   #define SQUARES 4
 26 
 27   // 將局面轉換成整數的基數,類似於康托擴展
 28 
 29   #define BASE 16                      
 30 
 31   // DFS的最大搜索深度
 32 
 33   #define DEPTHBOUND 30              
 34 
 35   // 路徑的最大長度(假設不會超過50,為了讓數組可以裝填下)
 36 
 37   #define STEPSBOUND 50                
 38 
 39         
 40 
 41   #define MOVE_LEFT (-1)
 42 
 43   #define MOVE_RIGHT 1
 44 
 45   #define MOVE_UP (-SQUARES)
 46 
 47   #define MOVE_DOWN SQUARES
 48 
 49   #define MOVE_NONE 0
 50 
 51  
 52 
 53   // 開啟一個曼哈頓距離的預算表       
 54 
 55   int manhattan[SQUARES * SQUARES][SQUARES * SQUARES];     
 56 
 57   // 后續的移動
 58 
 59   int move[SQUARES];     
 60 
 61         
 62 
 63   // 一個描述局面信息的數據結構
 64 
 65   struct node
 66 
 67  {
 68 
 69     // 利用vector容器裝填當前的狀態
 70 
 71    vector < int> state;    
 72 
 73     // 裝填一個最優解,就是行走的序列
 74 
 75     int moves[STEPSBOUND];
 76 
 77     // 當前的深度            
 78 
 79     int depth;                    
 80 
 81     // 當前的節點的估計值(用於啟發式搜索)
 82 
 83     int score;                     
 84 
 85     // 空格的位置
 86 
 87     int blank;                     
 88 
 89  };
 90 
 91         
 92 
 93   // 優先隊列的比較函數,分值較小的在優先隊列的前端(運算符重載)
 94 
 95   bool  operator<(node x, node y)
 96 
 97  {
 98 
 99     return x.score > y.score;
100 
101  }
102 
103         
104 
105   // 求絕對值
106 
107   int absi( int x)
108 
109  {
110 
111     return x >=  0 ? x:(-x);
112 
113  }
114 
115         
116 
117   // 判斷給定局面是否可解,利用上述的策略,就是逆序對的奇偶性原則
118 
119   bool solvable(vector < int> tiles)
120 
121  {
122 
123     int sum =  0, row;
124 
125     for ( int i =  0; i < tiles.size(); i++)
126 
127    {
128 
129       int tile = tiles[i];
130 
131            if (tile ==  0)
132 
133           {
134 
135             row = (i / SQUARES +  1);
136 
137              continue;
138 
139      }
140 
141       for ( int m = i; m < tiles.size(); m++)
142 
143              if (tiles[m] < tile && tiles[m] !=  0)
144 
145               sum++;
146 
147    }
148 
149     return !((sum + row) %  2);
150 
151  }
152 
153  
154 
155   /*   
156 
157     得到當前局面的后繼走法。MOVE_LEFT = 向左移動空滑塊,MOVE_RIGHT = 向右移動空滑塊,
158 
159     MOVE_UP = 向上移動空滑塊,MOVE_DOWN = 向下移動空滑塊
160 
161    */
162 
163  
164 
165   void valid_moves(node &current)
166 
167  {
168 
169     // 獲取后繼走法,但除去退回到該狀態的上一步的走法
170 
171           int last_move = MOVE_NONE;
172 
173           // 搜索深度確定之后,就可以確定最后一步在哪里了
174 
175           if (current.depth)
176 
177                    last_move = current.moves[current.depth -  1];
178 
179          memset(move, MOVE_NONE,  sizeof(move));
180 
181           // 嘗試四個方向,這四個方向的移動都有范圍的合法性制約
182 
183           if (current.blank % SQUARES >  0 && last_move != MOVE_RIGHT)
184 
185            move[ 0] = MOVE_LEFT;;
186 
187           if (current.blank % SQUARES < (SQUARES -  1) && last_move != MOVE_LEFT)
188 
189            move[ 1] = MOVE_RIGHT;
190 
191           if (current.blank / SQUARES >  0 && last_move != MOVE_DOWN)
192 
193            move[ 2] = MOVE_UP;
194 
195           if (current.blank / SQUARES < (SQUARES -  1) && last_move != MOVE_UP)
196 
197            move[ 3] = MOVE_DOWN;
198 
199  }
200 
201         
202 
203   // 將棋面狀態轉換為一個整數(保證每一個狀態都有一個唯一的最優值)
204 
205  unsigned  long  long key(vector < int> &tiles)
206 
207  {
208 
209    unsigned  long  long key =  0;
210 
211     for( int i =  0; i < tiles.size(); i++)
212 
213      key = key * BASE + tiles[i];
214 
215     return key;
216 
217  }
218 
219         
220 
221   // 從局面 current 執行 move 所指定的走法
222 
223  node execute(node &current,  int move)
224 
225  {
226 
227    node successor;
228 
229     // 走法步驟設定
230 
231    memcpy(successor.moves, current.moves,  sizeof(current.moves));
232 
233    successor.depth = current.depth+ 1;
234 
235    successor.moves[current.depth] = move;
236 
237     // 局面狀態設定,按 move 指定方向移動,交換空滑塊位置(這里等於是交換兩個方塊的位置)
238 
239    successor.state = current.state;
240 
241    successor.blank = current.blank+move;
242 
243    successor.state[current.blank] = successor.state[successor.blank];
244 
245    successor.state[successor.blank] =  0;
246 
247     return successor;
248 
249  }
250 
251  
252 
253   /*
254 
255     由於 h*(n) 在算法中非常關鍵,而且它是高度特化的,根據問題的不同而不同,所以需要找到一個合適
256 
257     的 h*(n) 函數是比較困難的,在這里使用的是每個方塊到其目標位置的曼哈頓距離之和,曼哈頓距離是
258 
259     該狀態要達到目標狀態至少需要移動的步驟數。g*(n) 為到達此狀態的深度,在這里采用了如下評估函
260 
261     數: f*(n) = g*(n) + 4 / 3 * h*(n),h*(n) 為當前狀態與目標狀態的曼哈頓距離,亦可
262 
263     以考慮計算曼哈頓配對距離。本題中實驗了一下,效率比單純曼哈頓距離要高,但比曼哈頓距離乘以適當系
264 
265     數的方法低。可參考:
266 
267     [Bernard Bauer,The Manhattan Pair Distance Heuristic for the 15-Puzzle,1994]
268 
269    */
270 
271  
272 
273   // 這里的評價函數綜合地考慮到了曼哈頓距離和搜索的深度,所以比較完美
274 
275   int score(vector < int> &state,  int depth)
276 
277  {
278 
279     int hn =  0;
280 
281     for ( int i =  0; i < state.size(); i++)
282 
283       if (state[i] >  0)
284 
285        hn += manhattan[state[i] -  1][i];
286 
287     return (depth +  4 * hn /  3);
288 
289  }
290 
291         
292 
293   // 判斷是否已達到目標狀態。
294 
295   bool solved(vector <  int > &state)
296 
297  {
298 
299     // 考慮最后一個元素是否是0,首先考慮最后一個元素,其實起到了一種剪枝的作用
300 
301     if (state[SQUARES * SQUARES -  1] !=  0)
302 
303       return  false;
304 
305     for( int i =  0; i < SQUARES * SQUARES -  1; i++)
306 
307       if (state[i] != (i +  1))
308 
309          return  false;
310 
311     return  true;
312 
313  }
314 
315         
316 
317   // 找到局面狀態的空滑塊位置
318 
319   int find_blank(vector <  int > &state)
320 
321  {
322 
323     for( int i =  0; i < SQUARES * SQUARES; i++)
324 
325       if (state[i] ==  0)
326 
327              return i;
328 
329  }

 

 

 

 

  1   /*   
  2 
  3     [深度優先搜索]——DFS
  4 
  5     與廣度優先搜索不同的是使用棧來保存開放集。對於移動步數較少(15步左右)時可以較快的得到解,但是
  6 
  7     隨着解移動步數的增加,得到解的時間及使用的內存都會大大增加,所以對於本題來說,不是有效的解決辦
  8 
  9     法。是否能得到解和解的深度限制有關,如果選擇的深度不夠大,可能不會得到解,若過大,將導致搜索時
 10 
 11     間成倍增加
 12 
 13    */
 14 
 15  
 16 
 17   bool solve_puzzle_by_depth_first_search(vector < int> tiles,  int directions[])
 18 
 19  {
 20 
 21     // 定義一個堆棧的節點
 22 
 23    node copy;
 24 
 25    copy.state = tiles;
 26 
 27    copy.depth =  0;
 28 
 29    copy.blank = find_blank(tiles);
 30 
 31     // 將移動的數組清空
 32 
 33    memset(copy.moves, MOVE_NONE,  sizeof(copy.moves));
 34 
 35     // 檢測當前局面是否為已解決狀態。
 36 
 37     if (solved(copy.state))
 38 
 39    {
 40 
 41      memcpy(directions, copy.moves,  sizeof(copy.moves));
 42 
 43            return  true;
 44 
 45    }
 46 
 47     // 將初始狀態放入開放集中
 48 
 49    stack <node> open;
 50 
 51    open.push(copy);
 52 
 53     // 閉合集
 54 
 55     set <unsigned  long  long> closed;
 56 
 57     while (!open.empty())
 58 
 59    {
 60 
 61       // 處理開放集中的局面,並將該局面放入閉合集中
 62 
 63      node current = open.top();
 64 
 65           open.pop();
 66 
 67           closed.insert(key(current.state));
 68 
 69            // 獲取該局面的后繼走法,后繼走法都會加入開放集中
 70 
 71           valid_moves(current);
 72 
 73            for ( int i =  0; i < SQUARES; i++)
 74 
 75           {
 76 
 77              if(move[i] == MOVE_NONE)
 78 
 79                continue;
 80 
 81              // 在當前局面上執行走法
 82 
 83             node successor = execute(current, move[i]);
 84 
 85              // 如果已經訪問,嘗試另外一種走法(利用閉合集來裝填已經訪問的路徑,避免了用一個很大的數組來標識)
 86 
 87              if(closed.find(key(successor.state)) != closed.end())
 88 
 89                continue;
 90 
 91              // 記錄求解中前一步走法,如果找到解,那么立即退出。否則的話在限制的
 92 
 93               // 深度內將其加入開放集   
 94 
 95              if(solved(successor.state))
 96 
 97             {
 98 
 99               memcpy(directions, successor.moves,  sizeof(successor.moves));
100 
101                      return  true;
102 
103        }
104 
105         // 將當前局面放入開放集中(這里對其深度進行一定程度的剪枝)
106 
107         if(successor.depth < DEPTHBOUND)
108 
109               open.push(successor);
110 
111       }
112 
113    }
114 
115     return  false;
116 
117  }

 

  

 

  1   /*   
  2 
  3     [廣度優先搜索]——BFS
  4 
  5     對於移動步數較少(15步左右)時可以較快的得到解,但是隨着解移動步數的增加,得到解的時間及使用的
  6 
  7     內存都會大大增加,所以對於本題來說,不是有效的解決辦法
  8 
  9    */
 10 
 11  
 12 
 13   bool solve_puzzle_by_breadth_first_search(vector <  int > tiles,  int directions[])
 14 
 15  {
 16 
 17    node copy;
 18 
 19    copy.state = tiles;
 20 
 21    copy.depth =  0;
 22 
 23    copy.blank = find_blank(tiles);
 24 
 25    memset(copy.moves, MOVE_NONE,  sizeof(copy.moves));
 26 
 27     // 檢測當前局面是否為已解決狀態
 28 
 29     if(solved(copy.state))
 30 
 31    {
 32 
 33      memcpy(directions, copy.moves,  sizeof(copy.moves));
 34 
 35            return  true;
 36 
 37    }
 38 
 39     // 將初始狀態放入開放集中
 40 
 41    queue < node > open;        
 42 
 43    open.push(copy);
 44 
 45     // 閉合集
 46 
 47     set < unsigned  long  long > closed;
 48 
 49     while (!open.empty())
 50 
 51    {
 52 
 53       // 處理開放集中的局面,並將該局面放入閉合集中
 54 
 55      node current = open.front();
 56 
 57           open.pop();
 58 
 59           closed.insert(key(current.state));
 60 
 61            // 獲取該局面的后繼走法,后繼走法都會加入開放集中
 62 
 63           valid_moves(current);
 64 
 65            for ( int i =  0; i < SQUARES; i++)
 66 
 67           {
 68 
 69              if(move[i] == MOVE_NONE)
 70 
 71                continue;
 72 
 73              // 在當前局面上執行走法
 74 
 75             node successor = execute(current, move[i]);
 76 
 77              // 如果已經訪問,嘗試另外一種走法
 78 
 79              if(closed.find(key(successor.state)) != closed.end())
 80 
 81                continue;
 82 
 83              // 記錄求解中前一步走法,如果找到解,那么立即退出 
 84 
 85              if(solved(successor.state))
 86 
 87        {
 88 
 89               memcpy(directions, successor.moves,  sizeof(successor.moves));
 90 
 91                return  true;
 92 
 93             }
 94 
 95              // 將當前局面放入開放集中
 96 
 97             open.push(successor);
 98 
 99      }
100 
101    }
102 
103     return  false;
104 
105  }

 

 

 

 

  1   /*   
  2 
  3     [A* 搜索]——樓天成之百度之星ASTAR
  4 
  5     深度優先搜索和寬度優先搜索都是盲目的搜索,並沒有對搜索空間進行剪枝,導致大量的狀態必須被檢測,
  6 
  7     A* 搜索在通過對局面進行評分,對評分低的局面優先處理(評分越低意味着離目標狀態越近),這樣充分
  8 
  9     利用了時間,在盡可能短的時間內搜索最有可能達到目標狀態的局面,從而效率比單純的 DFS 和 BFS 要
 10 
 11     高,不過由於需要計算評分,故啟發式函數的效率對於 A* 搜索至關重要,值得注意的是,即使較差的啟
 12 
 13     發式函數,也能較好地剪枝搜索空間。對於復雜的局面狀態,必須找到優秀的啟發式函數才可能在給定時間
 14 
 15     和內存限制下得到解,如果給定復雜的初始局面,而沒有優秀的啟發式函數,則由於 A* 搜索會存儲產生的
 16 
 17     節點,大多數情況下,在能找到解之前會由於問題的巨大狀態數而導致內存耗盡
 18 
 19    */
 20 
 21  
 22 
 23   bool solve_puzzle_by_a_star(vector < int> tiles,  int directions[])
 24 
 25  {
 26 
 27    node copy;
 28 
 29    copy.state = tiles;
 30 
 31    copy.depth =  0;
 32 
 33    copy.blank = find_blank(tiles);
 34 
 35     // 這里記錄各個狀態的評分,分值越低越好
 36 
 37    copy.score = score(copy.state,  0);
 38 
 39    memset(copy.moves, MOVE_NONE,  sizeof(copy.moves));
 40 
 41     // A*搜索利用優先隊列priority_queue容器來存儲開放集 
 42 
 43    priority_queue < node > open; 
 44 
 45    open.push(copy);
 46 
 47     // 閉合集利用map容器進行映射
 48 
 49    map <unsigned  long  longint>closed;
 50 
 51     while (!open.empty())
 52 
 53    {
 54 
 55       // 刪除評價值最小的節點,標記為已訪問過
 56 
 57           node current = open.top();
 58 
 59           open.pop();
 60 
 61            // 將狀態特征值和狀態評分存入閉合集中(一個狀態對應唯一的一個評分)
 62 
 63           closed.insert(make_pair<unsigned  long  longint> (key(current.state), current.score));
 64 
 65            // 如果是目標狀態,返回
 66 
 67            if(solved(current.state))
 68 
 69           {
 70 
 71             memcpy(directions, current.moves, sizeof(current.moves));
 72 
 73              return  true;
 74 
 75           }
 76 
 77            // 計算后繼走法,更新開放集和閉合集(4個方向搜索)
 78 
 79           valid_moves(current);
 80 
 81            for( int i =  0; i < SQUARES; i++)
 82 
 83           {
 84 
 85              if(move[i] == MOVE_NONE)
 86 
 87                continue;
 88 
 89         // 移動滑塊,評價新的狀態
 90 
 91             node successor = execute(current, move[i]);
 92 
 93              // 根據啟發式函數對當前狀態評分(根據狀態很深度進行評分)
 94 
 95             successor.score = score(successor.state, successor.depth);
 96 
 97            
 98 
 99         /*
100 
101           A*算法的局限性:
102 
103                如果當前狀態已經訪問過,查看是否能夠以更小的代價達到此狀態,如果沒
104 
105                有,繼續,否則從閉合集中提出並處理。在深度優先搜索中,由於可能后面
106 
107                生成的局面評分較高導致進入閉合集,從棧的底端生成同樣的局面,評分較
108 
109                低,但是由於較高評分的局面已經在閉合集中,評分較低的等同局面將不予
110 
111                考慮,這可能會導致深度搜索 “漏掉” 解
112 
113          */
114 
115            
116 
117         // 不斷迭代,直到得到一個最優解                 
118 
119        map < unsigned  long  longint >::iterator it = closed.find(key(successor.state));
120 
121         if (it != closed.end())
122 
123             {
124 
125           if (successor.score >= (*it).second)
126 
127                        continue;
128 
129               closed.erase(it);
130 
131             }
132 
133              // 當前局面放入開放集中
134 
135             open.push(successor);
136 
137           }
138 
139    }
140 
141     return  false;
142 
143  }

 

 

   

/*   

   [IDA* 搜索]

   深度優先在內存占用方面占優勢,但是由於沒有剪枝,導致搜索空間巨大,A* 搜索雖然剪枝,但是由於存

   儲了產生的每個節點,內存消耗較大。IDA* 搜索結合了兩者的優勢。IDA* 實質上就是在深度優先搜索的

   算法上使用啟發式函數對搜索深度進行限制

 
*/

 

  bool solve_puzzle_by_iterative_deepening_a_star(vector <  int > tiles, int directions[])

 {

   node copy;

   copy.state = tiles;

   copy.depth =  0;

   copy.blank = find_blank(tiles);

   memset(copy.moves, MOVE_NONE,  sizeof(copy.moves));

        

    // 檢測當前局面是否為已解決狀態

    if(solved(copy.state))

   {

     memcpy(directions, copy.moves,  sizeof(copy.moves));

           return  true;

   }

  

    // 設定初始搜索深度為初始狀態的評分

    int depth_limit =  0, min_depth = score(copy.state,  0);

    while( true)

   {

      // 獲取迭代后的評分

           if(depth_limit < min_depth)

            depth_limit = min_depth;

           else

             // 開始搜索第一層

            depth_limit++;

          numeric_limits< int> t;

          min_depth = t.max();

           // 開始新的深度優先搜素,深度為 depth_limit

     stack < node > open;

          open.push(copy);

           while (!open.empty())

          {

            node current = open.top();

            open.pop();

             // 獲取該局面的后繼走法,后繼走法都會加入開放集中

            valid_moves(current);

             for ( int i =  0; i < SQUARES; i++)

            {

               if (move[i] == MOVE_NONE)

                       continue;

          // 在當前局面上執行走法

                    node successor = execute(current, move[i]);

               // 記錄求解中前一步走法,如果找到解,那么立即退出。否則的話在限制的深度內將其加入開放集   

                     if(solved(successor.state))

                    {

                      memcpy(directions, successor.moves,  sizeof(successor.moves));

                       return  true;

                    }

               // 計算當前節點的評分,若小於限制,加入棧中,否則找超過的最小值

                    successor.score = score(successor.state, successor.depth);

               if(successor.score < depth_limit)

                      open.push(successor);

                     else

                    {

                       if (successor.score < min_depth)

                        min_depth = successor.score;

                    }

            }                   

          }

   }

    return  false;

 }

        

  void solve_puzzle(vector < int> tiles)

 {

    // 這里給出了BFS,DFS,A*和IDA*一共四種方法

    int moves[STEPSBOUND];

    //  深度優先搜索。

   
//  solve_puzzle_by_depth_first_search(tiles, moves);

   
//  寬度優先搜索。

   
//  solve_puzzle_by_breadth_first_search(tiles, moves);

   
//  A* 搜索。解長度在 30 - 50 步之間的局面平均需要 7 s。UVa RT 1.004 s。

   
//  solve_puzzle_by_a_star(tiles, moves);

   
//  IDA* 搜索。解長度在 30 - 50 步之間的局面平均需要 1.5 s。UVa RT 0.320 s。

   solve_puzzle_by_iterative_deepening_a_star(tiles, moves);

    //  輸出走法步驟。

    for ( int i =  0; i < STEPSBOUND; i++)

   {

      // 有一種情況是初始狀態本身就是目標狀態,這種情況下,我們利用MOVE_NONE來標識

      if (moves[i] == MOVE_NONE)

             break;

           switch (moves[i])

          {

             case MOVE_LEFT:

              cout <<  " L ";

               break;

             case MOVE_RIGHT:

              cout <<  " R ";

                break;

             case MOVE_UP:

              cout <<  " U ";

                break;

             case MOVE_DOWN:

              cout <<  " D ";

                     break;

     }

   }

   cout << endl;

 }

        

  // 預先計算曼哈頓距離填表(曼哈頓距離為兩者的橫坐標之差的絕對值與縱坐標之差的絕對值之和)

  void cal_manhattan( void)

 {

    for ( int i =  0; i < SQUARES * SQUARES; i++)

      for ( int j =  0; j < SQUARES * SQUARES; j++)

          {

             int tmp =  0;

            tmp += (absi(i / SQUARES - j / SQUARES) + absi(i % SQUARES - j % SQUARES));

            manhattan[i][j] = tmp;

          }     

 }

        

  int main( int ac,  char *av[])

 {     

    // 計算曼哈頓距離,填表。

   cal_manhattan();

    int n, tile;

    // 利用vector容器裝填一個完整的局面

   vector < int> tiles;

    // 讀入樣例       

   cin >> n;

    while (n--)

   {

      // 讀入局面狀態

     tiles.clear();

      for ( int i =  0; i < SQUARES * SQUARES; i++)

          {

            cin>>tile;

       tiles.push_back(tile);

          }

      // 判斷是否有解,無解則輸出相應信息,有解則使用相應算法解題

           if(solvable(tiles))

       solve_puzzle(tiles);

           else

            cout <<  " This puzzle is not solvable. " << endl;

   }

    return  0;

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM