HDU 1043 八数码问题
Posted 三人行
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HDU 1043 八数码问题相关的知识,希望对你有一定的参考价值。
附上题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1043
我用了3种方法AC。第一种是双向广搜 + 逆序对奇偶剪枝 + 康拓展开 。 第二种方法是打表法,先用bfs搜素出所有路径,保存。当然还有康拓展开。第二种速度快多了。
第三种方法 A*算法 。关于A*算法大家可以参考大神的博客。
第一种 用时 1880MS 代码如下:
#include <bits/stdc++.h> #define MAX 400000 #define N 9 using namespace std; /* 双向广搜,解决八数码问题。 这题还要 进行奇偶剪枝,只有当初态和终态的逆序对和奇偶性相同才有解。 终态逆序对和为0 是偶数,所以在搜索时,某个状态是奇那么肯定不行。不必保存该状态了 */ int fac[] = {1,1,2,6,24,120,720,5040,40320,362880}; char f[]={\'r\',\'l\',\'u\',\'d\'};//0,1,2,3 左右上下 int fc[4][2]={{0,1},{0,-1},{-1,0},{1,0}}; struct no { int id; short c; }dis[MAX];//记录路径 struct node { string str; int id; node (const string s1,const int t):str(s1),id(t){}; node(){ } }; queue<node> q1; queue<node> q2; int vis[MAX]; string Sstr = "123456789"; string Tstr = "123456789"; string news = "123456789"; int Tstr_id ,Sstr_id; int found = 0; int ANS_t,ANS_o; int op; //奇偶剪枝 bool pruning(string str) { int sum = 0; for (int i=0;i<N;++i) { if (str[i] == \'9\')continue; for (int j=i+1;j<N;++j) if (str[j]!=\'9\' && str[j]<str[i]) sum++; } if (sum%2) return false; return true; } int kt(string a) { int ans = 0; for (int i = 0;i<N;++i) { int t =0; for (int j=i+1;j<N;++j) if (a[j] < a[i]) t++; ans+= t*fac[N-i-1]; } return ans; } bool change(string & s,int v,int i) { int x = i/3; int y = i%3; int cx = x+fc[v][0]; int cy = y+fc[v][1]; if (cx >=0 && cx < 3 && cy>=0 && cy < 3) { int j = cx*3 + cy; news = s; char t = news[j]; news[j] = news[i]; news[i] = t; //形成新状态后,检测该状态的奇偶性 if (pruning(news)) return true; else return false; } else return false; } int put (int i) { if (i==0) return 1; if (i==1) return 0; if (i==2) return 3; if (i==3) return 2; } //双向bfs 的扩展 void dbfs_expend(node & s,bool Flag,int g,int i) { if (!change(s.str,i,g)) return; int v = kt(news); if (Flag) //正向 { if (vis[v]!=1) { if (vis[v] == 2)//在队列二中出现过,也就是双在此交汇了 { ANS_t = v; ANS_o = s.id; op = i;//衔接操作 found = 1; return ; } vis[v] = 1; dis[v].c = i;dis[v].id = s.id; q1.push(node(news,v)); } } else //反向 { if (vis[v]!=2) { if (vis[v]==1) //交汇了 { ANS_t = s.id; ANS_o = v; op = put(i); found = 1; return ; } vis[v] = 2; dis[v].c = put(i);dis[v].id = s.id; q2.push(node(news,v)); } } } void dbfs() { while(!q1.empty()) q1.pop(); while(!q2.empty()) q2.pop(); node s1(Sstr,Sstr_id); node s2(Tstr,Tstr_id); q1.push(s1); //正向搜索队列 q2.push(s2); //反向搜索队列 vis[Sstr_id] = 1; vis[Tstr_id] = 2; node temp; while(!q1.empty() || !q2.empty()) { if (!q1.empty()) { temp = q1.front();q1.pop(); int g = 0; while(temp.str[g]!=\'9\') ++g; for (int i=0;i<4;++i) { dbfs_expend(temp,true,g,i); if (found) return;//找到了 } } if (!q2.empty()) { temp = q2.front();q2.pop(); int g = 0; while(temp.str[g]!=\'9\') ++g; for (int i=0;i<4;++i) { dbfs_expend(temp,false,g,i); if (found) return ;//找到了 } } } } void init() { memset(vis,0,sizeof(vis)); found = 0; } void putans() { if (!found) printf ("unsolvable\\n"); else { char ans[MAX]; int i = 0; int j = ANS_o; while(j != Sstr_id) { ans[i++] = f[dis[j].c]; j = dis[j].id; } for (int j=i-1;j >= 0; --j) //这里注意,ans数组的存储顺序,很明显,要倒着输出 { printf ("%c",ans[j]); } //中间有个衔接步骤,别忘了输出 printf ("%c",f[op]) ; //接着从 ANS_t 到回去,把从终点出发扩散的道路输出 j = ANS_t; while(j != Tstr_id) { printf ("%c",f[dis[j].c]) ; j = dis[j].id; } printf ("\\n"); } } int main () { char ch; Tstr_id = kt(Tstr); while(cin >> ch) { if (ch == \'x\') { Sstr[0]=\'9\'; } else Sstr[0] = ch; for (int i=1;i<=8;++i) { cin >> ch; if (ch == \'x\') { Sstr[i] = \'9\'; } else Sstr[i] = ch; } if(pruning(Sstr)) { Sstr_id = kt(Sstr); init(); dbfs(); putans(); } else printf("unsolvable\\n"); } return 0; } /* ullddrurdllurdruldr ullddrurdllurdruldr */
第二种方法: 最初用的string 来存储状态字符,影响了速度,改为char数组后速度提升了
速度提升不少。可喜可贺啊。
string代码如下
#include <bits/stdc++.h> #define MAX 400000 #define N 9 using namespace std; /* bfs + 打表预处理 + 奇偶剪枝,解决八数码问题。 这题还要 进行奇偶剪枝,只有当初态和终态的逆序对和奇偶性相同才有解。 终态逆序对和为0 是偶数,所以在搜索时,某个状态是奇那么肯定不行。不必保存该状态了 */ int fac[] = {1,1,2,6,24,120,720,5040,40320,362880}; char f[]={\'r\',\'l\',\'u\',\'d\'};//0,1,2,3 左右上下 int fc[4][2]={{0,1},{0,-1},{-1,0},{1,0}}; struct no { int id; short c; no(){ id = -1;c = -1; } }dis[MAX];//记录路径 struct node { string str; int id; node (const string s1,const int t):str(s1),id(t){}; node(){ } }; queue<node> q1; string Sstr = "123456789"; string Tstr = "123456789"; string news = "123456789"; int Tstr_id ,Sstr_id; int kt(string a) { int ans = 0; for (int i = 0;i<N;++i) { int t =0; for (int j=i+1;j<N;++j) if (a[j] < a[i]) t++; ans+= t*fac[N-i-1]; } return ans; } bool change(string & s,int v,int i) { int x = i/3; int y = i%3; int cx = x+fc[v][0]; int cy = y+fc[v][1]; if (cx >=0 && cx < 3 && cy>=0 && cy < 3) { int j = cx*3 + cy; //计算出移动到的位置的下标 news = s; char t = news[j]; news[j] = news[i]; news[i] = t; return true; } return false; } int put(int i) { if (i==0) return 1; if (i==1) return 0; if (i==2) return 3; if (i==3) return 2; } void bfs() { node s1(Tstr,Tstr_id); q1.push(s1); // dis[Tstr_id].id = Tstr_id; node temp; while(!q1.empty()) { temp = q1.front();q1.pop(); int g = 0; while(temp.str[g]!=\'9\') ++g; for (int i=0;i<4;++i) { if (!change(temp.str,i,g)) continue; int v = kt(news); if (dis[v].id == -1) { dis[v].c = put(i); dis[v].id = temp.id; q1.push(node(news,v)); } } } } void putans() { int j = Sstr_id; while(j != Tstr_id) { printf ("%c",f[dis[j].c]); j = dis[j].id; } printf ("\\n"); } int main () { char ch; Tstr_id = kt(Tstr); bfs(); while(cin >> ch) { if (ch == \'x\') { Sstr[0]=\'9\'; } else Sstr[0] = ch; for (int i=1;i<=8;++i) { cin >> ch; if (ch == \'x\') { Sstr[i] = \'9\'; } else Sstr[i] = ch; } Sstr_id = kt(Sstr); if(dis[Sstr_id].id != -1) putans(); else printf("unsolvable\\n"); } return 0; } /* ullddrurdllurdruldr ullddrurdllurdruldr */
char 数组版 最快版本
1 #include <bits/stdc++.h> 2 #define MAX 400000 3 #define N 9 4 using namespace std; 5 /* 6 bfs + 打表预处理 ,解决八数码问题。 7 没有用string 速度快多了 8 */ 9 int fac[] = {1,1,2,6,24,120,720,5040,40320,362880};//康拓展开所需的阶乘 10 int fc[][2]={{1,0},{-1,0},{0,-1},{0,1}}; 11 char f[]="dulr"; 12 13 struct no 14 { 15 int id; //上一个的id 记录下来 16 short c;//操作 17 no() {id = -1;c = -1;} 18 }dis[MAX];//记录路径是结构体 19 20 struct node 21 { 22 char str[10];//状态 23 int id;//状态对应的id 24 node(){} 25 }; 26 27 queue<node> q1; 28 int Tstr_id = 0; 29 int Sstr_id; 30 31 int kt(node a)//康拓展开的计算函数 32 { 33 int ans = 0; 34 for (int i = 0;i<N;++i) 35 { 36 int t =0; 37 for (int j=i+1;j<N;++j) 38 if (a.str[j] < a.str[i]) t++; 39 ans+= t*fac[N-i-1]; 40 } 41 return ans; 42 } 43 44 int put(int i) //因为我们从终态逆着到其他各种能到达的状态,所以,只有倒过来操作, 45 { //在输出答案时就很方便了 46 if (i==0) return 1; 47 if (i==1) return 0; 48 if (i==2) return 3; 49 if (i==3) return 2; 50 } 51 52 void bfs() 53 { 54 node s1; 55 for (int i=0;i<9;++i) s1.str[i] = \'0\'+i+1;s1.id=0; 56 q1.push(s1); //起点 “123456789” 57 dis[Tstr_id].id = Tstr_id; 58 node temp,v; 59 while(!q1.empty()) 60 { 61 temp = q1.front();q1.pop(); 62 int g = 0; 63 while(temp.str[g]!=\'9\') ++g;//找到\'9\'的位置 64 for (int i=0;i<4;++i) 65 { 66 v = temp; 67 int cx = g/3+fc[i][0]; 68 int cy = g%3+fc[i][1]; 69 if (cx <0 || cx >= 3 || cy < 0 || cy >= 3) continue; 70 swap(v.str[g],v.str[cx*3 + cy]);//交换,状态变换完成,形成新的状态字符 71 v.id = kt(v); 72 if (dis[v.id].id == -1)//该编号不存在父节点 73 { // 路径的记录 74 dis[v.id].c = put(i); 75 dis[v.id].id = temp.id; 76 q1.push(v); 77 } 78 } 79 } 80 } 81 82 void putans() 83 { 84 int j = Sstr_id; 85 while(j != Tstr_id) 86 { 87 printf ("%c",f[dis[j].c]); 88 j = dis[j].id; 89 } 90 printf ("\\n"); 91 } 92 93 int main () 94 { 95 char ch; 96 bfs(); 97 node s; 98 while(cin >> ch) 99 { 100 if (ch == \'x\') 101 s.str[0]=\'9\'; 102 else s.str[0] = ch; 103 for (int i=1;i<=8;++i) 104 { 105 cin >> ch; 106 if (ch == \'x\') 107 s.str[i] = \'9\'; 108 else s.str[i] = ch; 109 } 110 Sstr_id = kt(s); 111 if(dis[Sstr_id].id != -1) 112 putans(); 113 else 114 printf("unsolvable\\n"); 115 } 116 return 0; 117 }
第三种:
A* 算法解决 ,可以参考别人代码,我的乱七八糟的。https://blog.csdn.net/xiaosshhaa/article/details/54315981
Hdu 1043 Eight (八数码问题)
HDU 1043 Eight八数码解题思路(bfs+hash 打表 IDA* 等)