这道题是一道经典的搜索题
第一次做A*算法的题目
这道题需要知道的,第一是怎样把全排列转换为数字,第二是h函数的设计
全排列转化为数字用到的是康托展开,跟逆序数有关,这是一个比较经典的转换。转换成数字的目的就是为了状态的判重,所以A*算法的空间需求总是指数级的
h函数用的是曼哈顿距离的总和,然后f=g+h,g函数就是从初始状态到现在状态花费的步骤数了。
然后使用优先队列,进行BFS。
这道题目,POJ上的数据还是很弱的。所以一般都0ms就水过了,普通的BFS也能很快搜过去。
貌似这道题目是不存在无解的情况,判别的方法其实也有。
判别方法是:
以数组为一维的举例子.
将八数码的一个结点表示成一个数组a[9],空格用0表示,设临时函数p(x)定义为:x数所在位置前面的数比x小的数的个数,
其中0空格不算在之内,那设目标状态为b[9],那r=sigma(p(x)) sigma()表示取所有的x:1-8并求和,
那对于初始状态a[9],t=sigma(p(x)),如果r和t同为奇数或者同为偶数,那么该状态有解,否则无解。
/* ID: sdj22251 PROG: inflate LANG: C++ */ #include <iostream> #include <vector> #include <list> #include <map> #include <set> #include <deque> #include <queue> #include <stack> #include <bitset> #include <algorithm> #include <functional> #include <numeric> #include <utility> #include <sstream> #include <iomanip> #include <cstdio> #include <cmath> #include <cstdlib> #include <cctype> #include <string> #include <cstring> #include <cmath> #include <ctime> #define MAXN 105 #define INF 1000000000 using namespace std; struct wwj { int pre, step, dir, d, pos, id; char x[12]; bool operator <(const wwj &a) const { return a.d < d; } wwj(){dir = -1;} wwj(int u, int v, int w, int y, char t[]) { pre = u; step = v; dir = w; pos = y; strcpy(x, t); } }p[400000]; int jc[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320}; int d[] = {-1, -3, 1, 3}; int v[400000]; char ans[]= "12345678x"; bool move[][4] = {{0,0,1,1}, {1,0,1,1}, {1,0,0,1}, {0,1,1,1}, {1,1,1,1}, {1,1,0,1}, {0,1,1,0}, {1,1,1,0}, {1,1,0,0}}; int hash(wwj &t) //将全排列转化为数字,序列值为0~n!-1,达到了完美的映射 { int h = 0; for(int i = 1; i < 9; i++) { int count = 0; for(int j = 0; j <i ; j++) if(t.x[j] > t.x[i]) count++; h += count * jc[i]; } return h; } int h(wwj &t) { int sum = 0; for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) { int k = i * 3 + j; if(t.x[k] == 'x') continue; sum += abs(i - (t.x[k] - '1') / 3) + abs(j - (t.x[k] - '1') % 3); } return sum; } void shuchu(wwj t) { char res[5000]; int cnt = 0; while(t.pre != -1) { //printf("%s\n", t.x); if(t.dir == 0) res[cnt++] = 'l'; else if(t.dir == 1) res[cnt++] = 'u'; else if(t.dir == 2) res[cnt++] = 'r'; else if(t.dir == 3) res[cnt++] = 'd'; t = p[t.pre]; } res[cnt] = '\0'; reverse(res, res + cnt); printf("%s\n", res); } priority_queue<wwj>q; int main() { wwj tmp; for(int i = 0; i < 9; i++) { scanf("%s", &tmp.x[i]); if(tmp.x[i] == 'x') tmp.pos = i; } tmp.step = 0; tmp.pre = -1; tmp.d = tmp.step + h(tmp); tmp.id = hash(tmp); p[tmp.id] = tmp; q.push(tmp); v[tmp.id] = 1; while(!q.empty()) { wwj A = q.top(); q.pop(); if(strcmp(A.x, ans) == 0) {shuchu(A); break;} for(int i = 0; i < 4; i++) { if(move[A.pos][i] == 0) continue; int xx = A.pos + d[i]; if(xx < 0 || xx >= 9) continue; char tx[12]; strcpy(tx, A.x); swap(tx[xx], tx[A.pos]); wwj B(A.id, A.step + 1, i, xx, tx); B.id = hash(B); if(v[B.id]) continue; v[B.id] = 1; B.d = h(B) + B.step; p[B.id] = B; q.push(B); } } return 0; }
然后是IDA*算法,IDA*是基于A*的迭代加深搜索,其特点是忽略大于某些深度的状态。如果有解,直接输出,无解的话,放宽松深度限制,继续搜索。
对比A*算法,IDA*无疑是优点巨大的,因为其不需要判重,所以就不需要存储状态,也就节省了巨大的空间。
我的理解就是,设置一个h函数,就是曼哈顿距离的总和,目标状态的h函数值必然是0,则八数码的解决过程总体来说应该是一个h值逐渐变小的过程,所以过程中一些大于这个h值的状态就不搜索了。如果说在这个限制下无解,那么就要放宽h值的限制,中途中可以适当的向一些较大的h值的状态搜索,这就要看,之前最多搜到了哪一步。然后把限制对应的进行修改。
/* ID: sdj22251 PROG: inflate LANG: C++ */ #include <iostream> #include <vector> #include <list> #include <map> #include <set> #include <deque> #include <queue> #include <stack> #include <bitset> #include <algorithm> #include <functional> #include <numeric> #include <utility> #include <sstream> #include <iomanip> #include <cstdio> #include <cmath> #include <cstdlib> #include <cctype> #include <string> #include <cstring> #include <cmath> #include <ctime> #define MAXN 105 #define INF 1000000000 using namespace std; struct wwj { int pos; char x[12]; }tmp; int top, found, st[400000], mi; int d[] = {-1, -3, 1, 3}; char as[] = "lurd"; bool move[][4] = {{0,0,1,1}, {1,0,1,1}, {1,0,0,1}, {0,1,1,1}, {1,1,1,1}, {1,1,0,1}, {0,1,1,0}, {1,1,1,0}, {1,1,0,0}}; int h(wwj &t) { int sum = 0; for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) { int k = i * 3 + j; if(t.x[k] == 'x') continue; sum += abs(i - (t.x[k] - '1') / 3) + abs(j - (t.x[k] - '1') % 3); } return sum; } void output() { for(int i = 1;i <= top; i++) printf("%c", as[st[i]]); printf("\n"); } void dfs(int p, int dp, int maxdp) { int f = h(tmp); int i, t; if(mi > f) mi = f; if(f + dp > maxdp || found) return; if(f == 0) { output(); found = 1; return; } for(i = 0;i < 4; i++) { if(move[p][i] == 0) continue; t = d[i] + p; if(top > 0 && d[st[top]] + d[i] == 0) continue; //一个重要的剪枝,如果之前的一步和现在的一步是相反的,无疑是做了无用功,不必搜索。 swap(tmp.x[t], tmp.x[p]); st[++top] = i; dfs(t, dp + 1, maxdp); top--; swap(tmp.x[t], tmp.x[p]); } } void IDA() { int maxdp = h(tmp);//初始为初状态的h值 found = 0; while(found == 0) { mi = 0x7fffffff; //mi表示在当前的h值限制下所能搜到的最优位置,即h值最小的位置 top = 0; dfs(tmp.pos, 0, maxdp); maxdp += mi; //如果不能搜到就将mi加上去 } } int main() { for(int i = 0; i < 9; i++) { scanf("%s", &tmp.x[i]); if(tmp.x[i] == 'x') tmp.pos = i; } IDA(); return 0; }