八数码
1.题目
题目描述
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入格式
输入初始状态,一行九个数字,空格用0表示
输出格式
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)
样例输入 #1
283104765
样例输出 #1
4
2.分析
思路分析:
1.如何表示每次的状态?
用字符串表示
2.如何存储新状态?
哈希映射
3.此题本质?
bfs
3.代码
1. bfs (+queue) + unordered_map
#include <iostream>
using namespace std;
#include <queue> //队列
#include <unordered_map> //哈希表
#include <algorithm> //swap
string st;
int bfs()
{
string ed = "123804765";
queue<string> q; //队列
unordered_map<string, int> d; //距离初始状态的步数
//init
q.push(st);
d[st] = 0;
while (!q.empty())
{
//取出队头
auto t = q.front();
q.pop();
//是否达到目标
int dist = d[t];
if (t == ed) return dist;
//未达成目标,则找到'0',与四周进行交换
int k = t.find('0');
int x = k / 3, y = k % 3;
int dx[] = { -1,1,0,0 }, dy[] = { 0,0,-1,1 };
for (int i = 0; i < 4; ++i)
{
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < 3 && b >= 0 && b < 3)
{
swap(t[k], t[a * 3 + b]);
if (!d.count(t)) //新状态
{
//插入队列
q.push(t);
//更新 新状态 的距离
d[t] = dist + 1;
}
swap(t[k], t[a * 3 + b]); //回溯
}
}
}
队列为空时,仍然无法移动成功
//return -1;
}
int main()
{
cin >> st;
printf("%d", bfs());
return 0;
}
重点分析
int k = t.find('0'); //找到'0'所在的一维位置
int x = k / 3, y = k % 3; //转化为二维表示
2.双向bfs (适用于知道起始状态的情况)
思路分析
分别用两个队列存储从开头出发的点 ,和 , 从结尾出发遍历的点
当,遍历过同一个点的时候,说明已经找到了最小步数
#include <iostream>
using namespace std;
#include <queue>
#include <unordered_map>
#include <map>
string st;
string ed = "123804765";
//双端队列
int dbfs()
{
queue<string> q1, q2;
unordered_map<string, int> d1, d2;
q1.push(st);
q2.push(ed);
d1[st] = 0;
d2[ed] = 0;
while (q1.size() && q2.size())
{
//从短的出发 !!! [由于是要求最短步数,每次从短的出发]
string t;
if (q1.size() <= q2.size())
{
t = q1.front();
q1.pop();
int dist = d1[t];
if (d2.count(t)) return dist + d2[t];
int k = t.find('0');
int x = k / 3, y = k % 3;
int dx[] = { -1,1,0,0 }, dy[] = { 0,0,-1,1 };
for (int i = 0; i < 4; ++i)
{
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < 3 && b >= 0 && b < 3)
{
swap(t[k], t[a * 3 + b]);
if (!d1.count(t))
{
q1.push(t);
d1[t] = dist + 1;
}
swap(t[k], t[a * 3 + b]); //回溯
}
}
}
else
{
t = q2.front();
q2.pop();
//是否访问至同一个状态
int dist = d2[t];
if (d1.count(t)) return d1[t] + dist;
int k = t.find('0');
int x = k / 3, y = k % 3;
int dx[] = { -1,1,0,0 }, dy[] = { 0,0,-1,1 };
for (int i = 0; i < 4; ++i)
{
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < 3 && b >= 0 && b < 3)
{
swap(t[k], t[a * 3 + b]);
if (!d2.count(t))
{
q2.push(t);
d2[t] = dist + 1;
}
swap(t[k], t[a * 3 + b]);
}
}
}
}
//无法通过变换得到
return -1;
}
int main()
{
cin >> st;
printf("%d", dbfs());
return 0;
}
3.双向bfs优化
思路
由于构造两个队列中有重复性代码
其实可以用一个队列,但是额外构造哈希映射
用 0 ,1 ,2分别表示未被遍历,被顺序遍历,被逆序遍历
补充:注意特判!!!!!
#include <iostream>
using namespace std;
#include <queue>
#include <unordered_map>
#include <algorithm>
string st, ed = "123804765";
int dbfs()
{
//特判
if (st == ed) return 0;
queue<string> q;
unordered_map<string, int> d, v;
d[st] = 0;
d[ed] = 1;
v[st] = 1; //顺序访问过
v[ed] = 2; // 逆序访问过
q.push(st);
q.push(ed);
while (!q.empty())
{
//取出队头
string now, cur;
now = cur = q.front();
q.pop();
int k = now.find('0');
int x = k / 3, y = k % 3;
int dx[] = { -1,1,0,0 }, dy[] = { 0,0,-1,1 };
for (int i = 0; i < 4; ++i)
{
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < 3 && b >= 0 && b < 3)
{
swap(now[k], now[a * 3 + b]);
//顺序/逆序访问过了
if (v[now] == v[cur])
{
swap(now[k], now[a * 3 + b]);
continue;
}
//顺序+逆序访问过了
if (v[now] + v[cur] == 3) return d[now] + d[cur];
//否则 说明是某个方向的延伸
d[now] = d[cur] + 1;
v[now] = v[cur]; //标记
q.push(now);
swap(now[k], now[a * 3 + b]);
}
}
}
}
int main()
{
cin >> st;
printf("%d", dbfs());
return 0;
}
4.总结
bfs的深入学习
5.更新日志
2022.9.18 整理
欢迎交流、讨论、指正~
不正确、不理解之处欢迎评论留言~