双向广搜+hash+康托展开 codevs 1225 八数码难题
codevs 1225 八数码难题
时间限制: 1 s
空间限制: 128000 KB
题目等级 : 钻石 Diamond
题目描述 Description
Yours和zero在研究A*启发式算法.拿到一道经典的A*问题,但是他们不会做,请你帮他们.
问题描述
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入描述 Input Description
输入初试状态,一行九个数字,空格用0表示
输出描述 Output Description
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)
样例输入 Sample Input
283104765
样例输出 Sample Output
4
数据范围及提示 Data Size & Hint
详见试题
分类标签 Tags 点此展开
启发式搜索 广度优先搜索 深度优先搜索 迭代搜索 搜索
1 /*复杂题目模板加注解*/ 2 # include <stdio.h> 3 # include <mem.h> 4 5 # define MAXN (362880 + 5) 6 7 typedef struct 8 { 9 char a[9]; 10 }state; 11 12 const int dir[4][2] = {{-1,0}, {0,1}, {1,0}, {0,-1}}; 13 int fact[9]; 14 15 int front, rear; 16 state cur, nst; /* new state */ 17 char vis[MAXN]; 18 char dist[MAXN]; /* 求的是最短距离( < 100),可以用 char 类型 */ 19 state Q[MAXN/2]; 20 21 22 void read(state *s); 23 int inversions(state s); 24 int cantor(state s); 25 void init_fact(void); 26 int bfs_d(state start, state goal); 27 28 int main() 29 { 30 state start, goal; 31 32 freopen("in.txt", "r", stdin); 33 freopen("out.txt", "w", stdout); 34 35 init_fact(); 36 37 read(&start);/*指针引用*/ 38 read(&goal); 39 40 if (inversions(start)%2 == inversions(goal)%2)/ 41 {/*判断能不能到达最终状态,如果能达到最终状态的话,那么格子中的个数在后面有几个比他小的数的和的奇偶是不变的,无论怎么变换,可以用归纳推理证明*/ 42 printf("%d\n", bfs_d(start, goal)); 43 } 44 else puts("-1");/*找不到最终的状态*/ 45 46 return 0; 47 } 48 49 int bfs_d(state start, state goal) 50 { 51 int i, x, y, nx, ny, ct, nt; 52 53 memset(vis, 0, sizeof(vis)); 54 memset(dist, 0, sizeof(dist)); 55 56 front = 1; 57 Q[front] = start; 58 rear = 2; 59 Q[rear++] = goal; 60 vis[cantor(start)] = 1; /* 1 表示从起始节点扩展得到 */ 61 vis[cantor(goal)] = 2; /* 2 表示从目标节点扩展得到 */ 62 63 while (front < rear) 64 { 65 cur = Q[front++]; 66 ct = cantor(cur); 67 for (i = 0; cur.a[i] && i < 9; ++i);/*找出0的位置*/ 68 x = i / 3;/*求出0所在的行数列数*/ 69 y = i % 3; 70 for (i = 0; i < 4; ++i) 71 { 72 nx = x + dir[i][0];/*把0向四周扩展*/ 73 ny = y + dir[i][1]; 74 if (nx>=0 && nx<3 && ny>=0 && ny<3) 75 { 76 nst = cur; 77 nst.a[x*3+y] = cur.a[nx*3+ny];/*互换0的位置与对应元素的位置*/ 78 nst.a[nx*3+ny] = 0; 79 if (!vis[nt = cantor(nst)])/*判断当前这个状态是否已经到过*/ 80 { 81 Q[rear++] = nst; 82 /* foot[nt] = ct; */ 83 dist[nt] = dist[ct] + 1; 84 vis[nt] = vis[ct];/*转移扩展的方向*/ 85 } 86 else if (vis[ct] != vis[nt])/*如果已经到过,就是两者变换的次数和加上最后一次变化*/ 87 {/*这是双向广搜的精髓,判断两个点是从不同的方向转移来的*/ 88 return 1 + dist[nt] + dist[ct]; 89 } 90 } 91 } 92 } 93 94 return -1; 95 } 96 97 void read(state *s) 98 { 99 int i; 100 char c[5]; 101 102 for (i = 0; i < 9; ++i) 103 { 104 scanf("%s", c); 105 if (c[0] == 'x') (*s).a[i] = 0; 106 else (*s).a[i] = c[0] - '0'; 107 } 108 } 109 110 int inversions(state s) 111 { 112 char ch; 113 int i, j, ret; 114 115 ret = 0; 116 for (i = 0; i < 9; ++i) 117 { 118 if (s.a[i] == 0) continue; 119 ch = s.a[i]; 120 for (j = i+1; j < 9; ++j) 121 { 122 if (s.a[j] < ch && s.a[j] != 0) 123 ++ret; 124 } 125 } 126 127 return ret; 128 } 129 130 int cantor(state s)/*康托展开应用于哈希表,处理排列问题是不会有冲突的,网上有证明,可以自己看*/ 131 { 132 char ch; 133 int i, j, ret, cnt; 134 135 ret = 0; 136 for (i = 0; i < 9; ++i) 137 { 138 cnt = 0; 139 ch = s.a[i]; 140 for (j = i+1; j < 9; ++j) 141 { 142 if (s.a[j] < ch) 143 ++cnt; 144 } 145 ret += cnt*fact[8-i]; 146 } 147 148 return ret; 149 } 150 151 void init_fact(void) 152 { 153 int i; 154 155 fact[0] = 1; 156 for (i = 1; i < 9; ++i) 157 { 158 fact[i] = i * fact[i-1];/*处理阶乘,整张图一共有9!种状态*/ 159 } 160 }
本题题解:
1 #define N 3628800 2 #include<iostream> 3 #include<queue> 4 #include<cstdio> 5 #include<cstring> 6 using namespace std; 7 struct state{ 8 char a[10]; 9 }; 10 queue<state>que; 11 int xx[]={0,0,1,-1}; 12 int yy[]={1,-1,0,0}; 13 int visit[N]={0}; 14 int fact[10]={0}; 15 int dis[N]={0}; 16 void init_goal(state& goal) 17 { 18 goal.a[0]='1';goal.a[1]='2';goal.a[2]='3'; 19 goal.a[3]='8';goal.a[4]='0';goal.a[5]='4'; 20 goal.a[6]='7';goal.a[7]='6';goal.a[8]='5'; 21 //goal.a="123804765"; 22 //strcpy(goal.a,"123804765"); 23 } 24 void in_fact() 25 { 26 fact[0]=1; 27 for(int i=1;i<9;++i) 28 fact[i]=i*fact[i-1]; 29 } 30 int cantor(state k) 31 { 32 int ret=0; 33 for(int i=0;i<9;++i) 34 { 35 int cnt=0; 36 char ch=k.a[i]; 37 for(int j=i+1;j<9;++j) 38 if(ch>k.a[j]) cnt++; 39 ret+=cnt*fact[8-i]; 40 } 41 return ret; 42 } 43 int bfs(state begin,state goal) 44 { 45 que.push(begin);que.push(goal); 46 int k1=cantor(begin); 47 int k2=cantor(goal); 48 visit[k1]=1; 49 visit[k2]=2; 50 dis[k1]=0;dis[k2]=0; 51 while(!que.empty()) 52 { 53 state fro=que.front(); 54 int can_fro=cantor(fro); 55 que.pop(); 56 int i; 57 for(i=0;fro.a[i]!='0'&&i<9;++i); 58 int x=i/3; 59 int y=i%3; 60 for(int j=0;j<4;++j) 61 { 62 int nx=x+xx[j],ny=y+yy[j]; 63 if(nx>=0&&nx<3&&ny>=0&&ny<3) 64 { 65 state now=fro; 66 now.a[3*x+y]=fro.a[3*nx+ny]; 67 now.a[3*nx+ny]='0'; 68 int kj=cantor(now); 69 if(!visit[kj]) 70 { 71 que.push(now); 72 visit[kj]=visit[can_fro];/*转移是正向扩展还是逆向扩展*/ 73 dis[kj]=dis[can_fro]+1; 74 } 75 else if(visit[kj]!=visit[can_fro]) 76 {/*这是双向广搜的精髓,必须用visit表示当前这个是同时由正反广搜扩展来的*/ 77 return 1+dis[kj]+dis[can_fro]; 78 } 79 } 80 } 81 } 82 } 83 int main() 84 { 85 state goal,begin; 86 init_goal(goal); 87 in_fact(); 88 // scanf("%s",goal.a+1); 89 scanf("%s",begin.a); 90 printf("%d\n",bfs(begin,goal)); 91 return 0; 92 }