八数码问题(三种解决办法)
题目链接: https://www.luogu.org/problemnew/show/P1379
题目链接:https://vijos.org/p/1360 (题目一样,上面一个测试数据更多)
0.问题引入
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765)。
找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
1. 广度优先遍历
- 用字符串表示状态。
- 使用 map<string,bool> 进行判定当前状态是否进行过搜索。
- 记录进行到第几层BFS,需要一个 9! 的数组(0~8全排列的个数)。
这题是说明了不会有无解的情况,真正情况下是可能会有无解的情况的,出现在 队列为空还未走到目标状态。
#include <iostream> #include <string> #include <queue> #include <map> #include <algorithm> using namespace std; map<string, bool>m; int cnt[400000]; int head = 0, tail = 1; string aim = "123804765"; //1:up,2:down,3:left,4:right int moveto(int i, int index) { switch (i) { case 1: if (index - 3 >= 0)return index - 3; break; case 2: if (index + 3 <= 8)return index + 3; break; case 3: if (index % 3 != 0)return index - 1; break; case 4: if (index % 3 != 2)return index + 1; break; } return -1; } void BFS(string start) { if (start.compare(aim) == 0) { cout << '0' << endl; return; } bool flag = false; queue<string>q; q.push(start); while (!q.empty() && !flag) { string now = q.front(); q.pop(); int from = now.find('0'); //向四个方向 for (int i = 1; i <= 4; i++) { int to = moveto(i, from); //如果返回-1,当前方向无法到达 if (to == -1)continue; swap(now[from], now[to]); if (!m[now]) { if (now.compare(aim) == 0) { cout << cnt[head] + 1 << endl;//输出当前BFS的层数 flag = true; break; } cnt[tail++] = cnt[head] + 1; //cnt[tail++] = cnt[head] + 1; m[now] = true; q.push(now); } swap(now[from], now[to]); } head++; } } int main() { string start; cin >> start; BFS(start); return 0; }
2.DBFS 双向广度优先遍历
双向广度优先,两个队列,一个从起点开始扩展状态,另一个从终点开始扩展状态;如果两者相遇,则表示找到了一条通路,而且是最短的通路。
双向,必须要记录下来每个状态处于第几层,因为相遇的时候,必须知道这个相遇状态是处于BFS的哪一层。
#include <iostream> #include <string> #include <queue> #include <map> using namespace std; //记录该string状态处于正(反)向的第几层BFS map<string, int>fsign, rsign; int fcnt[300000]; int rcnt[300000]; string aim = "123804765"; //1:up,2:down,3:left,4:right int moveto(int i, int index) { switch (i) { case 1: if (index - 3 >= 0)return index - 3; break; case 2: if (index + 3 <= 8)return index + 3; break; case 3: if (index % 3 != 0)return index - 1; break; case 4: if (index % 3 != 2)return index + 1; break; } return -1; } bool extend(queue<string>&q, int head, int &tail, int direction) { string now = q.front(); q.pop(); int from = now.find('0'); for (int i = 1; i <= 4; i++) { int to = moveto(i, from); if (to != -1) { swap(now[from], now[to]); if (direction == 1) { if (!fsign[now]) { q.push(now); fsign[now] = fcnt[tail++] = fcnt[head] + 1; if (rsign[now]) { cout << fcnt[tail - 1] + rsign[now] << endl; return true; } } } else { if (!rsign[now]) { q.push(now); rsign[now] = rcnt[tail++] = rcnt[head] + 1; if (fsign[now]) { cout << rcnt[tail - 1] + fsign[now] << endl; return true; } } } swap(now[from], now[to]); } } return false; } void DBFS(string start) { if (start.compare(aim) == 0) { cout << '0' << endl; return; } bool flag = false; queue<string>forward, reverse; forward.push(start); reverse.push(aim); string forwardNow = start, reverseNow = aim; fsign[start] = 0; rsign[aim] = 0; int head = 0, tail = 1, rhead = 0, rtail = 1; while (!flag) { if (forward.size() <= reverse.size()) { if (extend(forward, head, tail, 1))return; head++; } else { if (extend(reverse, rhead, rtail, 2))return; rhead++; } } } int main() { string start; cin >> start; DBFS(start); return 0; }
3.A*(启发式搜索)
启发式搜索_百度百科
启发策略:f(n) = g(n) + h(n),其中 g(n) 为层数,h(n)为当前状态“不在位”的数量。建立优先级队列,每次选取 f(n) 最小的状态。
#include <iostream> #include <string> #include <map> #include <queue> using namespace std; string aim = "123804765"; map<string, bool>flag; int getDifferent(string s) { int cnt = 0; for (int i = 0; i < s.length(); i++) { if (s[i] != aim[i])cnt++; } return cnt; } class PAIR{ public: int floor;//层数 int cnt;//"不在位"的块数 string ss; PAIR(int floor, string s) { this->floor = floor; cnt = getDifferent(s); ss = s; } bool operator<(PAIR f)const { return (f.floor + f.cnt) < (floor + cnt); } }; priority_queue<PAIR>q; int moveto(int i, int index) { switch (i) { case 1: if (index - 3 >= 0)return index - 3; break; case 2: if (index + 3 <= 8)return index + 3; break; case 3: if (index % 3 != 0)return index - 1; break; case 4: if (index % 3 != 2)return index + 1; break; } return -1; } void BFS(string start) { if (start.compare(aim) == 0) { cout << "0" << endl; return; } q.push(PAIR(0, start)); int head = 0, tail = 1; bool sign = false; while (!q.empty()&&!sign) { PAIR p = q.top(); q.pop(); string now = p.ss; int from = now.find('0'); for (int i = 1; i <= 4; i++) { int to = moveto(i, from); if (to != -1) { swap(now[from], now[to]); if (!flag[now]) { if (now.compare(aim) == 0) { cout << p.floor + 1 << endl; sign = true; break; } flag[now] = true; q.push(PAIR(p.floor + 1, now)); } swap(now[from], now[to]); } } head++; } } int main() { string start; cin >> start; BFS(start); return 0; }
但是在这个题目,两个网站上跑得都更慢了,应该数据比较特殊。
附测试数据:
/* 273645801 out:15 053276184 out:21 836752104 out:14 */