以上,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問題。
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]== 0) continue;
16 for( int j=i+ 1;j< 16;j++)
17 {
18 if(cur_num[j]== 0) continue;
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]== 0) continue;
28 for( int j=i+ 1;j< 16;j++)
29 {
30 if(des_num[j]== 0) continue;
31 if(des_num[i]>des_num[j])
32 {
33 ni2++;
34 }
35 }
36 }
37 // 如果奇偶性相同的話,則說明是可以互相變化得到的
38 if(((ni1-ni2)& 1)== 0) return 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問題中的用數組來標記訪問節點的辦法,這樣可以避免了嚴重的超內存
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 ¤t)
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 ¤t, 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 }
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 }
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 }
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 long, int>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 long, int> (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 long, int >::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;
}