八数码是一道经典的搜索题,解法比较多,效率也不一样,今天我们来讲两种效率较高的解法——IDA*和双向BFS
题目来源:洛谷P1379
题目描述
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入输出格式
输入格式:
输入初始状态,一行九个数字,空格用0表示
输出格式:
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数
解法一:IDA*
对于这个问题,我们首先可以想到的就是直接DFS或BFS,根据实际测试,洛谷的数据可以用BFS水过,但是时间大都在8000ms左右
我们来考虑如何优化,发现引入一个估价函数h可以达到更好的效率,并且我们不难想到,用当前数字到正确位置的曼哈顿距离之和当做估价函数h,公式如下:
于是我们限定一个深度deep,如果当前的step + h > deep,我们就认为这是不合法的方案
通过这个优化,我们就可以提高搜索效率了(实际测试中时间在2000ms左右)
代码如下:
#include <cstdio> #include <cstring> #include <iostream> #include <cmath> #include <algorithm> using namespace std; string a; int stx,sty; int st[4][4]; int deep; int ans[10][2] = {{2,2},{1,1},{1,2},{1,3},{2,3},{3,3},{3,2},{3,1},{2,1}}; int judge; inline void init() { int k = 0; for(int i = 1;i < 4;i ++) for(int j = 1;j < 4;j ++) { st[i][j] = a[k ++] - '0'; if(st[i][j] == 0){ stx = i; sty = j; } } return ; } inline int target() { int exp = 0; for(int i = 1;i < 4;i ++) { for(int j = 1;j < 4;j ++) { if(!st[i][j]) continue; else exp += abs(i - ans[st[i][j]][0]) + abs(j - ans[st[i][j]][1]); } } return exp; } int dx[4] = {0,0,1,-1}; int dy[4] = {1,-1,0,0}; inline void Astar(int nx,int ny,int step) { int h = target(); if(step + h > deep) return ; if(h == 0){ judge = 1; return ; } for(int pro = 0;pro < 4;pro ++) { int fx = nx + dx[pro]; int fy = ny + dy[pro]; if(fx < 1||fx > 3||fy < 1||fy > 3) continue; swap(st[nx][ny],st[fx][fy]); Astar(fx,fy,step + 1); swap(st[nx][ny],st[fx][fy]); } } int main(int argc, char const *argv[]) { cin>>a; init(); if(!target()){ printf("0"); return 0; } while(++ deep) { Astar(stx,sty,0); if(judge){ printf("%d",deep); break; } } return 0; }
解法二:双向BFS
我们可以发现,题目已经将末状态给我们了,所以,我们可以使用双向BFS,从两端向中间搜索,我们可以使用hash等一系列技巧来优化算法,在这里我使用的是将矩阵转化为数列
在实际测试中,其效率也是非常的优秀(200ms~400ms)
代码如下:
#include <cstdio> #include <cstring> #include <queue> #include <algorithm> #include <map> #define ll long long using namespace std; const int ans = 123804765; int n; queue <int> q; int sqr[10][10]; int dx[4] = {-1,1,0,0}; int dy[4] = {0,0,1,-1}; map <int,int> vis; map <int,int> step; inline void bfs() { if(n == ans) { printf("0"); return ; } q.push(n); q.push(ans); vis[n] = 1; vis[ans] = 2; step[n] = 0; step[ans] = 1; while(!q.empty()) { ll state = q.front(); q.pop(); ll now = state; ll nx,ny; for(int i = 3;i ;i --) { for(int j = 3;j ;j --) { sqr[i][j] = now % 10; now /= 10; if(sqr[i][j] == 0) { nx = i; ny = j; } } } for(int i = 0;i < 4;i ++) { ll fx = nx + dx[i]; ll fy = ny + dy[i]; if(fx > 3||fx < 1||fy > 3||fy < 1) continue; swap(sqr[fx][fy],sqr[nx][ny]); ll New = 0; for(int i = 1;i <= 3;i ++) for(int j = 1;j <= 3;j ++) New = New * 10 + sqr[i][j]; if(vis[New] == vis[state]) { swap(sqr[fx][fy],sqr[nx][ny]); continue; } if(vis[New] + vis[state] == 3) { printf("%d",step[New] + step[state]); return ; } vis[New] = vis[state]; step[New] = step[state] + 1; q.push(New); swap(sqr[fx][fy],sqr[nx][ny]); } } } int main(int argc, char const *argv[]) { scanf("%d",&n); bfs(); return 0; }