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*,因为它比较短,也好找错。。。)

时间: 2025-01-02 14:04:33

HDU 1043 Eight八数码解题思路(bfs+hash 打表 IDA* 等)的相关文章

hdu 1043 Eight (八数码问题)【BFS】+【康拓展开】

<题目链接> 题目大意:给出一个3×3的矩阵(包含1-8数字和一个字母x),经过一些移动格子上的数后得到连续的1-8,最后一格是x,要求最小移动步数. 解题分析:本题用BFS来寻找路径,为了降低复杂度,用BFS从最终的目标状态开始处理,将所有搜索到状态以及对应的路径打表记录,然后对于输入的矩阵,直接查表输出答案 即可,本题还有一个难点,就是如何判断记录已经搜索过的状态,如果使用map+string(矩阵看成一维)会超时,所以我们这里用康拓展开式来记录状态. #include<cstdio

Hdu 1043 Eight (八数码问题)

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1043 题目描述: 3*3的格子,填有1到8,8个数字,还有一个x,x可以上下左右移动,问最终能否移动到12345678x的状态? hint:每一个3*3的格子从上到右,从左到右,一行一行读. 解题思路: 比较简单的八数码问题,大一暑假老师讲过,一直手懒脑懒并没有亲自尝试过.因为是多实例,先从12345678x的状态bfs出来所有可以到达的状态,并且记录下来路径.八数码最重要的就是保存状态,如果这个

HDU 2099 整除的尾数解题思路

整除的尾数 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 27774    Accepted Submission(s): 11767 Problem Description 一个整数,只知道前几位,不知道末二位,被另一个整数除尽了,那么该数的末二位该是什么呢? Input 输入数据有若干组,每组数据包含二个整数a,b(0<a<100

A*八数码

帮同学写的八数码,启发式搜索 创建两个表open,close,分别用的stl中的优先队列priority_queue和map,好久没写过代码了,bug调了半天 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <set> 5 #include <queue> 6 #include <algorithm> 7 #include <ve

HDU 1043 Eight (BFS&#183;八数码&#183;康托展开)

题意  输出八数码问题从给定状态到12345678x的路径 用康托展开将排列对应为整数  即这个排列在所有排列中的字典序  然后就是基础的BFS了 #include <bits/stdc++.h> using namespace std; const int N = 5e5, M = 9; int x[4] = { -1, 1, 0, 0}; int y[4] = {0, 0, -1, 1}; int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320

HDU 1043 POJ 1077 八数码问题

以下内容转载自:http://www.cnblogs.com/goodness/archive/2010/05/04/1727141.html 八数码的八境界 研究经典问题,空说不好,我们拿出一个实际的题目来演绎.八数码问题在北大在线测评系统中有一个对应的题,题目描述如下: Eight Time Limit: 1000MS    Memory Limit: 65536K  Special Judge Description The 15-puzzle has been aroundfor ove

HDU 1043 Eight(八数码)

p.MsoNormal { margin: 0pt; margin-bottom: .0001pt; text-align: justify; font-family: Calibri; font-size: 10.5000pt } h1 { margin-top: 5.0000pt; margin-bottom: 5.0000pt; text-align: center; font-family: 宋体; color: rgb(26,92,200); font-weight: bold; fo

BFS(八数码) POJ 1077 || HDOJ 1043 Eight

题目传送门1 2 题意:从无序到有序移动的方案,即最后成1 2 3 4 5 6 7 8 0 分析:八数码经典问题.POJ是一次,HDOJ是多次.因为康托展开还不会,也写不了什么,HDOJ需要从最后的状态逆向搜索,这样才不会超时.判重康托展开,哈希也可. POJ //#include <bits/stdc++.h> #include<iostream> #include<algorithm> #include<string> #include<stack

HDU 1043 八数码(A*搜索)

在学习八数码A*搜索问题的时候需要知道以下几个点: Hash:利用康托展开进行hash 康托展开主要就是根据一个序列求这个序列是第几大的序列. A*搜索:这里的启发函数就用两点之间的曼哈顿距离进行计算就可以. 减枝:在八数码里,任意交换一个空行和一个位置的数字,这个八数码的逆序数是不变的,这样就可以根据目前状态判断是否可达终点状态了. 第一次做这个题用的map进行哈希,结果果断超时,之后又写了LRJ书上的hash方法也超时了,最后只能用康托展开了 详细请参考:[八数码的八重境界] http://