HDU 1043 Eight八数码解题思路(bfs+hash 打表 IDA* 等)
Posted 糖栗子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HDU 1043 Eight八数码解题思路(bfs+hash 打表 IDA* 等)相关的知识,希望对你有一定的参考价值。
题目链接 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* 等)的主要内容,如果未能解决你的问题,请参考以下文章