poj 1077 A* IDA* 八数码 康托展开
题意:3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。给出初始状态和目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动方法。
poj1077 hdu1043 zoj1217 uva652 sgu139 (15数码)
首先,将空格看成数字9,对于任意的布局S,定义perm(S)为从上到下从左到右的一个排列,i的初始位置为(x,y) 目标位置为(x',y'),定义距离dist(i)=|x'-x| + |y'-y|,
每移动一个棋子,dist(9)=dist(9)±1,dist(9)的奇偶性发生了改变,又因为交换两个元素,perm(S)的逆序数的奇偶性改变,所以perm(S) + dist(9) 奇偶性不变,始终为偶数。
若perm(S) + dist(9)为奇数,则无解。
证明:定义t(i)为 Ai 的逆序数。考虑交换相邻元素 Ai 和 Aj (i+1=j),若 Ai < Aj,交换后t(i) = t(i)+1, t(j)不变,若 Ai > Aj,交换后t(i) 不变,t(j) = t(j) - 1.所以奇偶性改变。
对于不相邻的元素 Ai 和 Aj (i+k=j), Ai 依次和Ai+1、Ai+2、...Aj-1交换,一共交换了k-1次,然后和 Aj 交换1次, Aj 再依次和Aj-1、Aj-2...交换,也是k-1次,所以奇偶性一共改变了 2*(k-1) + 1 次,奇偶性改变。
A*:启发函数h()为空格以外的所有棋子到目标位置的dist()之和。
状态的表示要用到康托展开,如 {1,2,3} 按从小到大排列一共6个:123 132 213 231 312 321分别对应数字1、2、3、4、5、6.
本题用1 ~ 9!( 362880 )就可以表示所有排列,若用十进制表示则需要 876543210 个 .
const int PermSize = 9; int fac[] = { 0, 1, 2, 6, 24, 120,720, 5040, 40320, 362880 }; int Cantor(int buf[]) {//由排列得到序数 int res = 1;//序数从1开始 FOR(i, 0, PermSize-1) { int cnt = 0; FOR(j,i+1,PermSize) if(buf[i]>buf[j]) cnt++; res += cnt * fac[PermSize - i - 1]; } return res ; } int Cantor_2(int n, int num, int ans[]){//1~n的全排列 第num个 int c, cnt, space, vis[15]={0}; int pos=0; num--; //序数从1开始 FOD(i,n-1,0){ c=num/fac[i]; num=num%fac[i]; cnt=0; FOE(j,1,n) { if(vis[j]) continue; if(cnt++ == c){ vis[j]=1; ans[pos++]=j-1; if(j-1 == 8) space = pos - 1; break; } } } FOE(i,1,n) if(!vis[i]) { ans[pos++]=i-1; if(i-1 == 8) space = pos - 1; break; } return space; } //int dir[] = {-1, 3, 1, -3 };//Left down Right up char di[] = {'l','d','r','u'}; const int M = 362895; int a[9], space; int g[M], f[M], h[M], ps[M], p[M]; int nex[9][4]={ {-1, 3, 1,-1}, { 0, 4, 2,-1}, { 1, 5,-1,-1}, {-1, 6, 4, 0}, { 3, 7, 5, 1}, { 4, 8,-1, 2}, {-1,-1, 7, 3}, { 6,-1, 8, 4}, { 7,-1,-1, 5} }; /* bool is_ans(){ // 找到解 FOR(i,0,9) if(a[i]!=i) return false; return true; } */ void Print(int t){//输出解 if(p[t]==-1)return ; Print(ps[t]); printf("%c",di[p[t]]); } int H(){ //估价函数 int h=0; FOR(i,0,9) if(a[i]!=8) h += abs(i/3-a[i]/3) + abs(i%3-a[i]%3); return h; } struct cmp{ bool operator()(int a,int b){ return f[a] > f[b]; } }; priority_queue<int,vector<int>,cmp> q;//优先级队列 void solve(){ int t = Cantor(a); g[t] = 0; h[t] = H(); p[t] = -1; f[t] = g[t] + h[t]; while(!q.empty())q.pop(); q.push(t); while(!q.empty()){ t = q.top(); q.pop(); if(h[t] == 0){ Print(t); printf("\n"); return ; } space = Cantor_2(9, t, a);//将状态存入数组,同时记录空格的位置 FOR(d,0,4){ int next = nex[space][d]; if(next == -1) continue; swap(a[space], a[next]); int t1 = Cantor(a);//新状态 if(g[t1]==-1 || g[t1]>g[t]+1) { g[t1] = g[t] + 1; if(h[t1] == -1) h[t1] = H(); f[t1] = g[t1] + h[t1]; p[t1] = d; ps[t1] = t; q.push(t1); } if(h[t1] == 0){ Print(t1); printf("\n"); return ; } swap(a[space],a[next]); } } } bool readdata(){ //读入数据 memset(g,-1,sizeof g); memset(h,-1,sizeof h); char ch; int i=0; while(i<9 && cin>>ch){ if(ch=='x') {a[i]=8; space=i++;} else a[i++]=ch-'1'; } if(i >= 9) return true; return false; } bool no_answer(){ //无解 int s = 2-space/3 + 2-space%3; FOR(i,1,9) FOR(j,0,i) if(a[i]>a[j]) s++; if(s&1) return true; return false; } int main(){ #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); #endif while(readdata()){ if( no_answer() ) printf("%s\n","unsolvable"); else solve(); } return 0; }
IDA*:每次控制搜索的深度limit,若找不到解,则limit++。
int dir[] = {-1, 3, 1, -3 };//Left down Right up char di[] = {'l','d','r','u'}; int a[9], space; int path[362895], length, limit; int nex[9][4]={ {-1, 3, 1,-1}, { 0, 4, 2,-1}, { 1, 5,-1,-1}, {-1, 6, 4, 0}, { 3, 7, 5, 1}, { 4, 8,-1, 2}, {-1,-1, 7, 3}, { 6,-1, 8, 4}, { 7,-1,-1, 5} }; bool readdata(){ //读入数据 char ch; int i=0; while(i<9 && cin>>ch ){ //scanf("%c",&ch); while((ch<'1'||ch>'8') && ch!='x') scanf("%c",&ch); //cout<<i<<' '<<ch<<' '; if(ch=='x') { space=i; a[i++]=8;} else a[i++]=ch-'1'; //cout<<space<<endl; } if(i)return true; return false; } bool no_answer(){ //无解 int s = 2-space/3 + 2-space%3; //cout<<s<<' '<<space<<' '<<endl; FOR(i,1,9) FOR(j,0,i) if(a[i]>a[j]) s++; if(s&1) return true; return false; } int h(){ //估价函数 int h=0; FOR(i,0,9) if(a[i]!=8) h += abs(i/3-a[i]/3) + abs(i%3-a[i]%3); return h; } bool is_ans(){ // 找到解 FOR(i,0,9) if(a[i]!=i) return false; return true; } bool IDA_star(int len, int space){ if(len == limit){ length = len; if(is_ans()) return true; return false; } FOR(d,0,4){ int next=nex[space][d]; if(next == -1) continue; if(len && abs(path[len-1] - d) == 2 ) continue;//相反方向 swap(a[space], a[next]); path[len] = d; if(len + h() <= limit && IDA_star(len+1, next)) return true; swap(a[space],a[next]); } return false; } int main(){ while(readdata()){ if(no_answer()) {printf("%s\n","unsolvable"); continue;} limit = h(); while(!IDA_star(0, space)) limit++; FOR(i,0,length) printf("%c",di[path[i]]); printf("\n"); } return 0; }