八数码问题-记三种思路
八数码问题的难点是如何存储状态,进而判断某个状态是否访问过,这里记录一下我见到的几种方法
思路一 :bfs + unordered_map
#include<iostream>
#include<algorithm>
#include<unordered_map>
#include<queue>
using namespace std;
int bfs(string state){
queue<string> q;
unordered_map<string, int> d;//字符串(每个状态) → 整数(距离)(
q.push(state);
d[state] = 0;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
string end = "12345678x";
while (q.size()){
auto t = q.front();
q.pop();
if (t == end) return d[t];
int dis = d[t];
int k = t.find('x');//定位‘x’
int x = k / 3, y = k % 3;
for (int i = 0; i < 4; i ++ ){
int nx = x + dx[i], ny = y + dy[i];
if (nx >= 0 && nx < 3 && ny >= 0 && ny < 3){
swap(t[nx * 3 + ny], t[k]);//移动x
if (!d.count(t)){
d[t] = dis + 1;
q.push(t);
}
swap(t[nx * 3 + ny], t[k]);//回溯
}
}
}
return -1;// 如果不存在解决方案,则输出 -1。
}
int main(){
char s[2];//空格也要考虑
string state;
for (int i = 0; i < 9; i ++ ){
cin >> s;
state += *s;//忽略空格
}
cout << bfs(state) << endl;
return 0;
}
/*
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/48146/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
思路二:bfs + 康托展开
#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int LEN = 362880;//9的阶乘,总状态数
struct node {
int state[9];//记录一个八数码序列,即一个状态
int dis;//记录到起点的距离,即步数
};
int dir[4][2] = { {-1,0},{0,-1},{1,0},{0,1} };;//向左,向上,向右,向下。x轴向右,y轴向下,原点在左上角。
int visited[LEN] = { 0 };//记录每个状态, Cantor函数对它置数, 并且判重
int start[9];
int goal[9];
long int factory[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };//Cantor函数需要用到的常数
bool Cantor(int str[], int n) {
long result = 0;
for (int i = 0; i < n; i++) {
int counted = 0;
for (int j = i + 1; j < n; j++) {
if (str[i] > str[j]) {
++counted;
}
result += counted * factory[n - i - 1];
}
}
if (!visited[result]) {
visited[result] = 1;
return 1;
}
else
return 0;
}
int bfs() {
node head;
memcpy(head.state, start, sizeof(head.state));//复制起点状态
head.dis = 0;
queue<node> q;
Cantor(head.state, 9);
q.push(head);
while (!q.empty()) {
head = q.front();
if (memcmp(head.state, goal, sizeof(goal)) == 0) {
return head.dis;
}
q.pop();
int z;
for (z = 0; z < 9; z++) {//寻找0的位置(一维的)
if (head.state[z] == 0) {//找到了!
break;
}
}
//接着把状态转换成二维的
int x = z % 3;
int y = z / 3;
for (int i = 0; i < 4; i++) {
int newx = x + dir[i][0];
int newy = y + dir[i][1];//生成新的0的位置,即模拟二维,调整一维。
int nz = newx + 3 * newy;//转换为一维的
if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3) {
node newnode;
memcpy(&newnode, &head, sizeof(struct node));//复制新状态
swap(newnode.state[z], newnode.state[nz]);//把0移动到新的位置,在一维上操作免去了把一个二维数组麻烦地变为一维的
newnode.dis++;
if (Cantor(newnode.state, 9)) {
q.push(newnode);
}
}
}
}
return -1;
}
int main(void) {
for (int i = 0; i < 9; i++) {
cin >> start[i];
}
for (int i = 0; i < 9; i++) {
cin >> goal[i];
}
int num = bfs();
if (num != -1) {
cout << num << endl;
}
else {
cout << "-1" << endl;
}
return 0;
}
//来源:算法竞赛从入门到进阶
思路三:双向bfs + unordered_map
#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
int mat[3][3] = { 0 };//记录棋盘的一次性的全局变量,可以节省很多操作
int zx, zy;//记录0的位置的一次性的全局变量,可以节省很多操作
int dx[4] = { -1, 0, 1, 0 };
int dy[4] = { 0, -1, 0, 1 };
#define CHECK(x, y) (x >= 0 && x < 3 && y < 3 && y >= 0)
queue<int> q0, q1;
unordered_map <int, int> state;
unordered_map <int, int> ans;
void itom(int x) {//将一个九位数(棋盘)变成一个3乘3的矩阵
int div = 100000000;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
mat[i][j] = (x / div)%10;
if (!mat[i][j]) {//顺便记录0的位置
zx = j;
zy = i;
}
div = div / 10;
}
}
}
int mtoi(void) {//将一个矩阵变成一个九位整数
int chs = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
chs = chs * 10 + mat[i][j];
}
}
return chs;
}
void dbfs(int start, int end) {
if (start == end) {//特殊情况:起点即终点
cout << "YES!";
cout << 0;
return;
}
int flag, now;//flag用来判断新状态要加入哪个队列,now是队头
state[start] = 1, state[end] = 2;
ans[start] = 0, ans[end] = 0;
q0.push(start), q1.push(end);//双向bfs,双倍的初始化
while (!q0.empty() && !q1.empty()) {//得用&&还是||---前者
if (q0.size() > q1.size()) {//谁的元素少,就先操作谁
now = q1.front(), q1.pop();
itom(now);//把九位整数的队头转换为矩阵,同时获得0的坐标zx, zy
flag = 1;
}
else {
now = q0.front(), q0.pop();
itom(now);
flag = 0;
}
for (int i = 0; i < 4; i++) {
int nx = zx + dx[i];
int ny = zy + dy[i];
if (CHECK(nx, ny)) {
swap(mat[zy][zx], mat[ny][nx]);//0的移动
int k = mtoi();//矩阵转数值
if (!ans.count(k)) {//若未访问过,紧跟三个更新
state[k] = state[now];//标记更新
ans[k] = ans[now] + 1;//路程更新
if (flag == 0) {//队列更新
q0.push(k);
}
else if (flag == 1) {
q1.push(k);
}
}
else if(state[k] + state[now] == 3){//若已访问过
int cnt = ans[k] + ans[now] + 1;//这个+1可以画画图
cout << "YES!" << endl;
cout << cnt << endl;
return;
}
}
swap(mat[zy][zx], mat[ny][nx]);//回溯,恢复现场,恢复了现场才能进行其它的操作
}
}
cout << "NO" << endl;
return;
}
int main(void) {
/*测试矩阵与整数之间的相互转换
itom(876543021);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
cout << mat[i][j] << " ";
}
cout << endl;
}
cout << mtoi();
*/
int start = 0;
//start = 283104765;答案是4
int end = 123804765;
for (int i = 0; i < 9; i++) {
int j = 0;
cin >> j;
start = start * 10 + j;
}
dbfs(start, end);
return 0;
}
//来源:算法竞赛从入门到进阶