題目鏈接: 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 */