题目描述
OJ地址 http://lx.lanqiao.cn/problem.page?gpid=T42
如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。
我们把第一个图的局面记为:12345678.
把第二个图的局面记为:123.46758
显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
输入:
输入第一行包含九宫的初态,第二行包含九宫的终态。
输出:
输出最少的步数,如果不存在方案,则输出-1。
样例输入1:
12345678.
123.46758
样例输出1:
3
样例输入2:
13524678.
46758123.
样例输出2:
22
思路:
最短路问题,一般都用BFS来解,难点在于每个状态的确定,借着这个题可以很好的理解一下BFS的思想。在BFS当中,队列里的东西实际上是是每个“状态”,当状态达到了题目所说的终点“状态”时,就算是找到答案了。这个题的状态很明显是九宫格内数字的位置,所以,struct内要放入的东西就是当前九宫格的数字排列。 知道了这个点就比较好解题了。
我在结构体内部定义了一个二维数组,存储的就是当前的九宫格,还有一个方法是取出当前九宫格状态到string类型。另一个方法getpoint() 用于找到当前九宫格内 " . "的坐标。
朴素BFS代码
#include <bits/stdc++.h>
using namespace std;
map<string,bool> vis;
int dir[4][2] = {
{
-1,0},{
0,-1},{
1,0},{
0,1}};
struct nn{
int d;
char m[3][3];
string getstr(){
// 取出当前的局面 二维数组到string 用于比对目标局面
string t= "";
for(int i = 0; i < 9; i++){
t += m[i/3][i%3];
}
return t;
}
// 返回当前的.坐标
pair<int,int> getpoint(){
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
if(m[i][j] == '.'){
return make_pair(i,j);
}
}
}
}
} node;
int main() {
string sc,so;
cin >> sc;
cin >> so;
for(int i = 0; i < 9; i++){
node.m[i/3][i%3] = sc[i];
}
node.d = 0;
queue<nn> q;
q.push(node);
while(!q.empty()){
for(int i = 0;i < 4; i++){
node = q.front();
node.d = q.front().d + 1;
int x,y; // 取空格点坐标
x = node.getpoint().first;
y = node.getpoint().second;
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if( tx < 0 || tx >= 3 || ty < 0 || ty >= 3){
//出界
continue;
}
// 开始交换
node.m[x][y] = node.m[tx][ty];
node.m[tx][ty] = '.';
//交换以后判断是不是目标局面
if(node.getstr() == so){
cout << node.d << endl;
return 0;
}
if(!vis[node.getstr()]){
// 交换后的局面没出现过 标记一下,然后入队。
q.push(node);
vis[node.getstr()] = true;
}
}
q.pop();
}
cout << -1 << endl;
return 0;
}
CPU使用 609ms
内存使用 24.97MB
下面来看双向BFS,网上好多讲BFS的文章中的代码,有的变量复用了,读起来不是很容易理解,下边的代码个人感觉比较好理解,大致思路就是:两个队列q1 q2,q1从前往后扩展,q2从后往前扩展,每次扩展的时候,到底是扩展q1还是q2,就看这两个队列中哪个状态少,哪个少就扩展哪个,如果扩展的时候发现另一个队列已经扩展过这个状态了,那就是走通了,直接把双向走的步数相加就是结果。
双向BFS代码
#include <bits/stdc++.h>
using namespace std;
int dir[4][2] = {
{
-1,0},{
0,-1},{
1,0},{
0,1}};
map<string,int> vis1, vis2;
struct Node{
string str;
int step;
Node(){
step = 0;
str = "";
}
// 返回.的位置
int getZero(){
for(int i = 0; i < str.size(); i++){
if(str[i] == '.'){
return i;
}
}
}
} node;
bool check(int x, int y){
return x >= 0 and x < 3 and y >= 0 and y < 3;
}
int main(int argc, const char* argv[]){
string sc,so;
cin >> sc >> so;
queue<Node> q1,q2; //q1 正向 q2 反向
node.str = sc;
node.step = 0;
q1.push(node);
node.str = so;
q2.push(node);
while(!q1.empty() and !q2.empty()){
int cur = 0; // 扩展哪个队列 1 前向 2 反向
// 哪个少 扩哪个
if(q1.size() < q2.size()){
cur = 1;
node = q1.front();
} else{
cur = 2;
node = q2.front();
}
for(int i = 0; i < 4; i++){
int zero = node.getZero();
int x = zero / 3;
int y = zero % 3;
int dx = x + dir[i][0];
int dy = y + dir[i][1];
if(!check(dx, dy)){
continue;
}
// 交换两个位置的字符
int aim = dx * 3 + dy;
node.str[zero] = node.str[aim];
node.str[aim] = '.';
node.step++;
// 交换结束 入队
if(cur == 1){
if(vis2[node.str] != 0){
cout << node.step + vis2[node.str] << endl;
return 0;
}
}else{
if(vis1[node.str] != 0){
cout << node.step + vis1[node.str] << endl;
return 0;
}
}
if(cur == 1){
if(vis1[node.str] == 0){
vis1[node.str] = node.step;
q1.push(node);
}
node = q1.front();
}else{
if(vis2[node.str] == 0){
vis2[node.str] = node.step;
q2.push(node);
}
node = q2.front();
}
}
if(cur == 1){
q1.pop();
}else{
q2.pop();
}
}
cout << -1 << endl;
return 0;
}
CPU使用 15ms
内存使用 5.062MB
可见,双向BFS的效率相对于朴素BFS来说还是很高的,数据量越大,效果越明显,下边有一道类似的题目。
洛谷P1379 八数码难题
https://www.luogu.com.cn/problem/P1379
朴素BFS
#include <bits/stdc++.h>
using namespace std;
int dir[4][2] = {
{
-1,0},{
0,-1},{
1,0},{
0,1}};
map<string,int> vis;
struct Node{
string str;
int step;
Node(){
step = 0;
str = "";
}
// 返回0的位置
int getZero(){
for(int i = 0; i < str.size(); i++){
if(str[i] == '0'){
return i;
}
}
}
} node;
bool check(int x, int y){
return x >= 0 and x < 3 and y >= 0 and y < 3;
}
int main(int argc, const char* argv[]){
string sc;
cin >> sc;
queue<Node> q1;
node.str = sc;
node.step = 0;
q1.push(node);
while(!q1.empty()){
for(int i = 0; i < 4; i++){
node = q1.front();
if(node.str == "123804765"){
cout << node.step << endl;
return 0;
}
int zero = node.getZero();
int x = zero / 3;
int y = zero % 3;
int dx = x + dir[i][0];
int dy = y + dir[i][1];
if(!check(dx, dy)){
continue;
}
// 交换两个位置的字符
int aim = dx * 3 + dy;
node.str[zero] = node.str[aim];
node.str[aim] = '0';
node.step++;
// 交换结束 入队
if(vis[node.str] != 0){
continue;
}
vis[node.str] = node.step;
q1.push(node);
}
q1.pop();
}
return 0;
}
编程语言
C++
代码长度
1.20KB
用时
13.51s
内存
14.59MB
双向BFS
#include <bits/stdc++.h>
using namespace std;
int dir[4][2] = {
{
-1,0},{
0,-1},{
1,0},{
0,1}};
map<string,int> vis1, vis2;
const string END = "123804765";
struct Node{
string str;
int step;
Node(){
step = 0;
str = "";
}
// 返回0的位置
int getZero(){
for(int i = 0; i < str.size(); i++){
if(str[i] == '0'){
return i;
}
}
}
} node;
bool check(int x, int y){
return x >= 0 and x < 3 and y >= 0 and y < 3;
}
int main(int argc, const char* argv[]){
string sc;
cin >> sc;
if(sc == END){
cout << 0 << endl;
return 0;
}
queue<Node> q1,q2; //q1 正向 q2 反向
node.str = sc;
node.step = 0;
q1.push(node);
node.str = END;
q2.push(node);
while(!q1.empty() and !q2.empty()){
int cur = 0; // 扩展哪个队列 1 前向 2 反向
// 哪个少 扩哪个
if(q1.size() < q2.size()){
cur = 1;
node = q1.front();
} else{
cur = 2;
node = q2.front();
}
for(int i = 0; i < 4; i++){
int zero = node.getZero();
int x = zero / 3;
int y = zero % 3;
int dx = x + dir[i][0];
int dy = y + dir[i][1];
if(!check(dx, dy)){
continue;
}
// 交换两个位置的字符
int aim = dx * 3 + dy;
node.str[zero] = node.str[aim];
node.str[aim] = '0';
node.step++;
// 交换结束 入队
if(cur == 1){
if(vis2[node.str] != 0){
cout << node.step + vis2[node.str] << endl;
return 0;
}
}else{
if(vis1[node.str] != 0){
cout << node.step + vis1[node.str] << endl;
return 0;
}
}
if(cur == 1){
if(vis1[node.str] == 0){
vis1[node.str] = node.step;
q1.push(node);
}
node = q1.front();
}else{
if(vis2[node.str] == 0){
vis2[node.str] = node.step;
q2.push(node);
}
node = q2.front();
}
}
if(cur == 1){
q1.pop();
}else{
q2.pop();
}
}
return 0;
}
编程语言
C++
代码长度
1.91KB
用时
1.43s
内存
1.80MB