1. 程式人生 > >HDU 1043 Eight八數碼解題思路(bfs+hash 打表 IDA* 等)

HDU 1043 Eight八數碼解題思路(bfs+hash 打表 IDA* 等)

中間 節點 sca 技巧 length div clu 鏈接 output

題目鏈接 https://vjudge.net/problem/HDU-1043


經典的八數碼問題,學過算法的老哥都會拿它練搜索

題意:

給出每行一組的數據,每組數據代表3*3的八數碼表,要求程序復原為初始狀態

思路:

參加網站比賽時拿到此題目,因為之前寫過八數碼問題,心中暗喜,於是寫出一套暴力bfs+hash,結果TLE呵呵

思路一:bfs+hash(TLE)

技術分享

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <queue>
 4 #include <set>
 5 using namespace
std; 6 const int StMax=800000, HashMax=50000; 7 struct State{ 8 char map[3][3]; 9 int dis, fx, x, y, id, fa; 10 }start, st[StMax]; 11 int head[HashMax], mynext[StMax], dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; 12 char ch[4]={r, l, d, u}; 13 int myhash(State &a){ 14 a.id=0; 15 for (int
y=0; y<3; y++) 16 for (int x=0; x<3; x++) 17 a.id=a.id*10+((a.map[y][x]==x)?0:a.map[y][x])-0; 18 return a.id%HashMax; 19 } 20 int insert(int rear){ 21 int h=myhash(st[rear]), u=head[h]; 22 while(u){ 23 if (st[rear].id==st[u].id) return 0; 24 u=mynext[u];
25 } 26 mynext[rear]=head[h]; head[h]=rear; 27 return 1; 28 } 29 void output(int u){ 30 if (u==0) printf("unsolvable"); 31 else if (u==1) return; 32 else{ 33 output(st[u].fa); 34 printf("%c", ch[st[u].fx]); 35 } 36 } 37 38 int bfs(void){ 39 st[1]=start; insert(1); 40 if (start.id==123456780) return 1; 41 int front=1, rear=2;//2,1 for hash 42 while (front<rear){ 43 State &s=st[front]; 44 for (int i=0; i<4; i++){ 45 int nx=s.x+dir[i][0], ny=s.y+dir[i][1]; 46 47 if (nx<0 || nx>=3 || ny<0 || ny>=3) continue; 48 State &t=st[rear]; memcpy(&t, &s, sizeof(s)); 49 t.map[s.y][s.x]=s.map[ny][nx]; 50 t.map[ny][nx]=x; 51 if (!insert(rear)) continue; 52 t.x=nx; t.y=ny; t.fx=i; t.dis++; t.fa=front; 53 54 if (t.id==123456780) return rear; 55 rear++; 56 }front++; 57 } 58 return 0; 59 } 60 int input(void){ 61 char a[255]; int p=0, re; 62 if ((re=scanf("%[^\n]\n", a))!=1) return 0; 63 for (int y=0; y<3; y++) 64 for (int x=0; x<3; x++){ 65 while(a[p]== ) p++; 66 if ((start.map[y][x]=a[p])==x) {start.x=x; start.y=y;} 67 p++; 68 } 69 start.dis=0; 70 return 1; 71 } 72 73 int main(void){ 74 while (input()){ 75 memset(head, 0, sizeof(head)); 76 memset(mynext, 0, sizeof(mynext)); 77 output(bfs()); printf("\n"); 78 } 79 80 return 0; 81 }

看來hdu的數據比較強,比較多,考慮到八數碼問題狀態數不是非常大(<9!=362880<10^6)

(註:參考紫書 一般情況狀態總數小於10^6在可接受範圍)
於是考慮bfs的預處理打表,在此期間了解到康托展開用以編碼全排列

思路二:bfs打表+cantor(AC)

技術分享

中間三個數據分別是Time(ms) Mem(MB) Length

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <vector>
 4 using namespace std;
 5 typedef int State[9];
 6 const int STMAX=362880; 
 7 int fact[10]={1,1,2,6,24,120,720,5040,40320,362880}, dir[4][2]={0,-1,-1,0,0,1,1,0};
 8 int st[STMAX][9], vis[STMAX], myprev[STMAX], fx[STMAX], goal=46233, stcode[STMAX];
 9 char toch[4]={d,r,u,l};//反方向 
10 int encode(int map[], int n){
11     int code=0;
12     for (int i=0; i<n; i++){
13         int cnt=0;
14         for (int j=i+1; j<n; j++) 
15             if (map[i]>map[j]) cnt++;
16         code+=cnt*fact[n-1-i];
17     }return code; 
18 }
19 
20 int input(void){
21     char ch;
22     for (int i=0; i<9; i++){
23         do{if (scanf("%c", &ch)!=1) return 0;}while(ch== ||ch==\n);
24         if (ch==x||ch==X) ch=0;
25         st[0][i]=ch-0;
26     }
27     return 1;
28 }
29 
30 int check(void){
31     int sum=0;
32     for (int i=0; i<9; i++){
33         if (st[0][i]==0) continue;
34         for (int j=i+1; j<9; j++){
35             if (st[0][j]==0) continue;
36             if (st[0][i]>st[0][j]) sum++;
37         }
38     }
39     return sum;
40 }
41 
42 void show(vector<char> &path, int code){
43     if (code==goal) return;
44     else{
45         show(path, myprev[code]);
46         path.push_back(toch[fx[code]]);
47     }
48 }
49 
50 void pre(void){
51     memset(vis, 0, sizeof(vis));
52     memset(myprev, 0, sizeof(myprev));
53     State s={1,2,3,4,5,6,7,8,0}; memcpy(st[0], &s, sizeof(s));
54     vis[stcode[0]=encode(st[0], 9)]=1;
55     int front=0, rear=1;
56     while (front<rear){
57         State &a=st[front];
58         
59         int z=0; while (a[z]) z++;
60         for (int i=0; i<4; i++){
61             int nx=z%3+dir[i][0], ny=z/3+dir[i][1];
62             if (nx<0 || nx>2 || ny<0 || ny>2) continue;
63             State &b=st[rear]; memcpy(&b, &a, sizeof(a));
64             b[nx+ny*3]=0; b[z]=a[nx+ny*3];
65             
66             int code=encode(b, 9);
67             if (vis[code]) continue;
68             fx[code]=i; myprev[code]=stcode[front];
69             stcode[rear]=code; vis[code]=1; rear++;
70         }front++;
71     }
72 }
73 
74 int main(void){
75     pre();
76     while (input()){
77         vector<char> path;
78         int code=encode(st[0], 9);
79         if (!vis[code]) printf("unsolvable\n");
80         else {
81             show(path, code);
82             for (int i=path.size()-1; i>=0; i--)
83                 printf("%c", path[i]);
84             printf("\n");
85         }
86     }
87     
88     return 0;
89 }

解題到此結束,但在此期間想到過新學的IDA*,按結果來說也是不錯的

思路三:IDA*(AC)

技術分享
(沒錯,我特地重新上傳了一次,因為之前的代碼有不少啰嗦的地方)

我覺得此題用作IDA*的入門題目非常合適,dfs()中排除上次操作的反方向(prevDir)是一個很實用的小技巧,排除了許多分支

 1 #include <cstdio>
 2 #include <cmath>
 3 #include <cstring>
 4 #include <vector>
 5 using namespace std;
 6 typedef int State[9];
 7 State st, goal={1,2,3,4,5,6,7,8,0};
 8 int maxd;
 9 int isdir[4]={2,3,0,1}, orix[9]={2,0,1,2,0,1,2,0,1}, oriy[9]={2,0,0,0,1,1,1,2,2}, dir[4][2]={0,-1,-1,0,0,1,1,0};
10 char toch[4]={u, l, d, r};
11 int input(void){
12     char ch;
13     for (int i=0; i<9; i++){
14         do{if(scanf("%c", &ch)!=1) return 0;}while (ch== ||ch==\n);
15         if (ch==x) ch=0;
16         st[i]=ch-0;
17     }
18     return 1;
19 }
20 
21 int check(void){
22     int sum=0;
23     for (int i=0; i<9; i++){
24         if (st[i]==0) continue;
25         for (int j=i+1; j<9; j++){
26             if (st[j]==0) continue;
27             if (st[i]>st[j]) sum++;
28         }
29     }
30     return sum;
31 }
32 inline int calc(State &a){
33     int sum=0;
34     for (int i=0; i<9; i++)
35         sum+=abs(i%3-orix[st[i]])+abs(i/3-oriy[st[i]]);
36     return sum;
37 }
38 
39 int dfs(State &a, vector<char> &path, int z, int prevdir, int d){
40     int h=calc(a);
41     if (h==0) return 1;
42     if (maxd==d) return 0;
43     
44     if (h>1*(maxd-d)) return 0;
45     for (int i=0; i<4; i++){
46         if (prevdir!=-1 && isdir[prevdir]==i) continue;//great effect
47         int nx=z%3+dir[i][0], ny=z/3+dir[i][1];
48         if (nx<0 || nx>2 || ny<0 || ny>2) continue;
49         a[z]=a[nx+ny*3]; a[nx+ny*3]=0; path.push_back(toch[i]);
50         if (dfs(a, path, nx+ny*3, i, d+1)) return 1;
51         a[nx+ny*3]=a[z]; a[z]=0; path.pop_back();
52     }return 0;
53 }
54 
55 int main(void){
56     while (input()){
57         if (check()%2) {printf("unsolvable\n"); continue;}
58         int z=0; while(st[z]) z++;
59         for (maxd=0; ; maxd++){
60             vector<char> path;
61             if (dfs(st, path, z, -1, 0)){
62                 for (int i=0; i<path.size(); i++) printf("%c", path[i]);
63                 printf("\n");
64                 break;
65             }
66         }
67     }
68     return 0;
69 }

其他思路:

雙向BFS:

若需要路徑,則一定需判斷節點是否由另一隊列走過,並鏈接兩隊列中的路徑(考慮cantor)
A*+cantor:

使用priority_queue(優先隊列),啟發函數類似IDA*

(實際情況下我比較喜歡IDA*,因為它比較短,也好找錯。。。)

HDU 1043 Eight八數碼解題思路(bfs+hash 打表 IDA* 等)