八数码总结
2016-03-18 11:29
323 查看
题意:
八数码问题也称为九宫问题。编号为1~8的8个正方形滑块被摆成3行3列,棋盘上还有一个空格,每次可以把与空格相邻的滑块移到空格中,而它原来的位置就成了新的空格。给定初始局面和目标局面,计算出最少的移动步数。参考来源:八数码的八境界
方法1:
暴搜+queue,如果空格用’0’表示的话可以直接将状态压缩成一个9位整数,也可以用字符串表示状态,用结构体表示状态+步数+空格的位置。代码:
/* 264137058 815736402 */ #include<iostream> #include<map> #include<cstring> #include<queue> #include<cstdio> using namespace std; map<string, int>vis; int m, n; struct state{string str; int pos; int dist;}; state s; string goal; int dx[4] = {-1, 1, 0, 0}; int dy[4] = {0, 0, -1, 1}; int bfs() { queue<state>q; q.push(s); vis[s.str] = 1; while(!q.empty()){ state t = q.front();q.pop(); if(t.str.compare(goal) == 0) return t.dist; int p= t.pos; int x= p / 3, y = p % 3; for(int i = 0; i < 4; i++){ int newx = x + dx[i]; int newy = y + dy[i]; int newpos = newx * 3 + newy; string a = t.str; if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3){ swap(a[p], a[newpos]); if(vis[a]) continue; vis[a] = 1; q.push((state){a, newpos, t.dist + 1}); } } } return -1; } int main (void) { string st; int pos; cin>>st; cin>>goal; for(int i = 0; i < 9; i++){ if(st[i] == '0') pos = i; } s = (state){st, pos, 0}; int res = bfs(); if(res == -1) cout<<"No solution"<<endl; else cout<<res<<endl; }
方法2:
模拟队列+康拓展开/哈希/STL set判重,学习紫书上的代码~注意其中康拓展开
代码:
康拓展开
#include<iostream> #include<cstring> #include<set> #include<cstdio> using namespace std; typedef int state[9];//表示state 代表 int[9] const int maxn = 1e7 + 5; state st[maxn], sta, goal; int dist[maxn]; int dx[4] = {-1, 1, 0, 0}; int dy[4] = {0, 0, -1, 1}; /* 2 6 4 1 3 7 0 5 8 8 1 5 7 3 6 4 0 2 */ /******把0~8的全排列和0~362879的整数一一对应起来****/ int vis[400000], fact[10]; void init() { memset(vis, 0, sizeof(vis)); fact[0] = 1; for(int i = 1; i < 9; i++) fact[i] = fact[i - 1] * i; } int Insert(int s) { int code = 0; for(int i = 0; i < 9; i++){ int cnt = 0; for(int j = i + 1; j < 9; j++) if(st[s][j] < st[s][i]) cnt++; code += fact[8 - i] * cnt; } if(vis[code]) return 0; return vis[code] = 1; } int bfs() { init(); int fro = 0, rear = 1; dist[fro] = 0; Insert(fro); while(fro < rear){ state& s = st[fro]; if (memcmp(goal, s, sizeof(s)) == 0) return dist[fro]; int pos; for(pos = 0; pos < 9; pos++) if(!s[pos]) break; int x = pos / 3, y = pos % 3; for (int i = 0; i < 4; i++){ int newx = x + dx[i]; int newy = y + dy[i]; int newpos = newx * 3 + newy; if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3){ state& t = st[rear]; memcpy(&t, &s, sizeof(s)); t[newpos] = 0; t[pos] = s[newpos]; dist[rear] = dist[fro] + 1; if(Insert(rear)) rear++; } } fro++; } return -1; } int main (void) { for(int i = 0; i < 9; i++) cin>>st[0][i]; for(int i = 0; i < 9; i++) cin>>goal[i]; int res = bfs(); if(res != -1) cout<<res<<endl; else cout<<"No solution"<<endl; return 0; }
哈希:
const int hashsize = 1000003;//大质数 int head[hashsize], next[hashsize]; void init() { memset(head, 0, sizeof(head)); } int Hash(int s) { int tmp = 0; for(int i = 0; i < 9; i++) tmp = tmp * 10 + st[s][i]; return tmp % hashsize; } int Insert(int s) { int h = Hash(s); int u = head[h]; while(u){ if(memcpy(st[u], st[s], sizeof(st[s])) == 0) return 0; u = next[u]; } next[s] = head[h]; head[h] = s; return 1; }
set判重:
set<int>vis; void init() { vis.clear(); } int Insert(int s) { int v = 0; for(int i = 0; i < 9; i++) v = v* 10 + st[s][i]; if(vis.count(v)) return 0; vis.insert(v); return 1; }
方法3:
模拟队列+双向bfs~~这里判重随便选一个就好啦~~节约一半的时间和一半的空间,真心快多了~代码:
#include<iostream> #include<queue> #include<cstring> using namespace std; typedef int state[9]; const int maxn = 1e7 + 5; state st[maxn]; int dx[4] = {-1, 1, 0, 0}; int dy[4] = {0, 0, -1, 1}; int fact[maxn], vis[maxn], dist[3][maxn]; /* 2 6 4 1 3 7 0 5 8 8 1 5 7 3 6 4 0 2 */ void init() { memset(vis, 0, sizeof(vis)); fact[0] = 1; for(int i = 1; i < 9; i++) fact[i] = fact[i - 1] * i; } int canto(state s) { int code = 0; for(int i = 0; i < 9; i++){ int cnt = 0; for(int j = i + 1; j < 9; j++){ if(s[j] < s[i]) cnt++; } code += fact[8 - i] * cnt; } return code; } int bfs() { init(); int cs = canto(st[0]); int ct = canto(st[1]); dist[1][cs] = 0; dist[2][ct] = 0; vis[cs] = 1; vis[ct] = 2; int fro = 0, rear = 2; while(fro < rear){ state& s = st[fro]; cs = canto(s); int pos; for(pos = 0; pos < 9; pos++) if(!s[pos]) break; int x = pos / 3, y = pos % 3; for (int i = 0; i < 4; i++){ int newx = x + dx[i]; int newy = y + dy[i]; int newpos = newx * 3 + newy; if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3){ state& t = st[rear]; memcpy(&t, &s, sizeof(s)); t[pos] = s[newpos]; t[newpos] = s[pos]; ct = canto(t); if(!vis[ct]){ vis[ct] = vis[cs]; dist[vis[cs]][ct] = dist[vis[cs]][cs] + 1; rear++; }else if(vis[ct] != vis[cs]){ return dist[vis[ct]][ct] + dist[vis[cs]][cs] + 1; } } } fro++; } return -1; } int main (void) { for(int i = 0; i < 9; i++) cin>>st[0][i]; for(int i = 0; i < 9; i++) cin>>st[1][i]; int res = bfs(); if(res != -1) cout<<res<<endl; else cout<<"No solution"<<endl; return 0; }
方法4:
A* + 优先级队列(小顶堆)+ 曼哈顿距离A*搜索算法
当任何第二次走到一个点的时候,判断最小步骤是否小于记录的内容,如果是,则更新掉原最小步数,关于记录最小步数,如果使用曼哈顿距离作为h(n),则不必记录,这时的A*满足:每个点第一次被选出搜索队列时的状态为到达此点的最优解,而且每个点只会进入队列一次。所以只需要设定一个vis的数组就够了。
代码:
#include<iostream> #include<cstring> #include<cmath> #include<queue> /* 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 0 7 8 2 6 4 1 3 7 0 5 8 8 1 5 7 3 6 4 0 2 */ using namespace std; struct state { int a[9]; int pos; int f,g; int can; bool operator < ( const state s2)const { return f > s2.f; } }; const int maxn = 1e7 + 5; state sta, goal; int dx[4] = {-1, 1, 0, 0}; int dy[4] = {0, 0, -1, 1}; int fact[maxn], vis[maxn]; void init() { fact[0] = 1; for(int i = 1; i < 9; i++) fact[i] = fact[i - 1] * i; } int canto(state s) { int code = 0; for(int i = 0; i < 9; i++){ int cnt = 0; for(int j = i + 1; j < 9; j++){ if(s.a[j] < s.a[i]) cnt++; } code += fact[8 - i] * cnt; } return code; } int geth(state be) { int res = 0; for(int i = 0; i < 9; i++){ int tmp = goal.a[i]; for(int j = 0; j < 9; j++){ if(be.a[j] == tmp) res += abs(i/3 - j/3) + abs(i % 3 - j % 3); break; } } return res; } int astar() { priority_queue<state> q; q.push(sta); vis[sta.can] = 1; while(!q.empty()){ state s = q.top(); q.pop(); if(s.can == goal.can) return s.g; int x = s.pos / 3, y = s.pos % 3; for(int i = 0; i < 4; i++){ int newx = x + dx[i]; int newy = y + dy[i]; int newpos = newx * 3 + newy; if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3){ state t = s; t.a[t.pos] = s.a[newpos]; t.a[newpos] = s.a[t.pos]; t.pos = newpos; t.can = canto(t); if(vis[t.can]) continue; t.g = s.g + 1; t.f= geth(t) + t.g; q.push(t); vis[t.can] = 1; } } } return -1; } void print (state t) { for(int i = 0; i < 9; i++) cout<<t.a[i]<<' '; cout<<endl; cout<<"pos"<<t.pos<<endl; cout<<"can"<<t.can<<endl; cout<<"f"<<t.f<<endl; } int main (void) { init(); for(int i = 0; i < 9; i++){ cin>>sta.a[i]; if(sta.a[i] == 0){ sta.pos = i; } } sta.g = 0; sta.can = canto(sta); //print(sta); for(int i = 0; i < 9; i++) cin>>goal.a[i]; goal.can = canto(goal); //print(goal); int res = astar(); if(res != -1) cout<<res<<endl; else cout<<"No solution"<<endl; return 0; }
方法5:
IDA* + 曼哈顿距离IDA*即迭代加深的A*搜索,实现代码是最简练的,无须状态判重,无需估价排序。那么就用不到哈希表,堆上也不必应用,空间需求变的超级少。效率上,应用了曼哈顿距离。同时可以根据深度和h值,在找最优解的时候,对超过目前最优解的地方进行剪枝,这可以导致搜索深度的急剧减少,所以,这是一个致命的剪枝!因此,IDA*大部分时候比A*还要快,可以说是A*的一个优化版本!
代码:
#include<iostream> #include<queue> #include<cstring> #include<cmath> using namespace std; const int maxn = 1e7 + 5; int maxdepth; int dx[4] = {1, 0 , 0, -1}; int dy[4] = {0, 1, -1, 0}; /* 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 0 7 8 2 6 4 1 3 7 0 5 8 8 1 5 7 3 6 4 0 2 */ struct state { int a[9]; int pos; }; state st[maxn]; state goal, sta; int geth(state be) { int res = 0; for(int i = 0; i < 9; i++){ int tmp = goal.a[i]; if(tmp == 0) continue; for(int j = 0; j < 9; j++){ if(be.a[j] == tmp){ res += abs(i/3 - j/3) + abs(i % 3 - j % 3); break; } } } return res; } void print(state s) { for(int i = 0; i < 9; i++) cout<<s.a[i]<<' '; cout<<endl; } int dfs(state s, int h, int depth, int prei) { if(h == 0) return 1; // print(s); if(depth + h > maxdepth) return 0; int x = s.pos / 3, y = s.pos % 3; for (int i = 0; i < 4; i++){ if(prei + i == 3) continue; int newx = x + dx[i]; int newy = y + dy[i]; int newpos = newx * 3 + newy; if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3){ state t = s; t.a[s.pos] = s.a[newpos]; t.a[newpos] = s.a[s.pos]; t.pos = newpos; if(dfs(t, geth(t), depth + 1, i)) return 1; } } return 0; } int idastar() { int h = geth(sta); maxdepth = h; while(!dfs(sta, h, 0, -1) && maxdepth < 100) { maxdepth++; } return maxdepth; } int main (void) { for(int i = 0; i < 9; i++){ cin>>sta.a[i]; if(sta.a[i] == 0){ sta.pos = i; } } for(int i = 0; i < 9; i++){ cin>>goal.a[i]; } int res = idastar(); if(res < 100) cout<<res<<endl; else cout<<"No solution"<<endl; return 0; }
方法6:
有关逆序数空格在同一行上进行移动,1~8的排列逆序数不变,空格在同一列上进行移动,1~8的排列逆序数发生变化的对数为偶数。所以初始状态的逆序数与目标态同奇偶则有解,反之无解;
这一优化可以用在上面的所有方法中,也是一个很重要的剪枝。
代码:
int Reverse(state s) { int cnt = 0; for(int i = 0; i < 9; i++){ int tmp = s.a[i]; if(tmp == 0) continue; for(int j = i +1; j < 9; j++){ if(s.a[j] && s.a[i] > s.a[j]) cnt++; } } return cnt % 2; }
真的是细节决定一切。。。竟然写了一天。。。一半时间还都用来调试。。。写的时候就应该心思细腻,检查的时候模拟一遍过程,任何细节都不能放过!
相关文章推荐
- 容纳40位数的n!值
- GitHub 优秀的 Android 开源项目
- 机器学习资源分享
- 高效分布式计算系统:Spark
- 模拟器那些
- kill命令
- PhP 基础
- 支持外部命令的shell实现(glob、strsep、fork)
- 分分钟掌握设计基本原则
- 【NOIP2012TG】 开车旅行 详解+代码
- Ibatis中的动态SQL:isNotNull,isPropertyAvailable,isNotEmpty用法
- Swift比Objective-C有什么优势?
- POJ 1611 The Suspects (并查集)
- C Function
- Swift和Objective-C的联系
- 机器学习中的数据清洗与特征处理综述
- 最近流行的MVP模式
- iOS开发之蓝牙通信
- test lab ~ triangle test by using junit and coverage
- 【Spring】学习SpringAOP