在一个 3×33×3 的网格中,1∼81∼8 这 88 个数字和一个 x
恰好不重不漏地分布在这 3×33×3 的网格中。
例如:
1 2 3
x 4 6
7 5 8
在游戏过程中,可以把 x
与其上、下、左、右四个方向之一的数字交换(如果存在)。
我们的目的是通过交换,使得网格变为如下排列(称为正确排列):
1 2 3
4 5 6
7 8 x
例如,示例中图形就可以通过让 x
先后与右、下、右三个方向的数字交换成功得到正确排列。
交换过程如下:
1 2 3 1 2 3 1 2 3 1 2 3
x 4 6 4 x 6 4 5 6 4 5 6
7 5 8 7 5 8 7 x 8 7 8 x
现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。
输入格式
输入占一行,将 3×33×3 的初始网格描绘出来。
例如,如果初始网格如下所示:
1 2 3
x 4 6
7 5 8
则输入为:1 2 3 x 4 6 7 5 8
输出格式
输出占一行,包含一个整数,表示最少交换次数。
如果不存在解决方案,则输出 −1−1。
输入样例:
2 3 4 1 5 x 7 6 8
输出样例
19
难度:中等
时/空限制:1s / 64MB
总通过数:56081
总尝试数:81443
来源:
算法标签
我的代码:
/*
先分析好解决这个问题的步骤:
将一个随机的棋盘通过移动形成一个给定的状态,记录每种方案的步骤,得出最少的步骤数
bfs适合求边权值为1的最小步数模型
这里每走一步,都有最多三个方向可以选择,形成一个选择树,可用bfs或dfs
每走一步都会产生新的状态,这里的状态就是棋盘的状态,对应树中的结点,就是要入队的元素
所以BFS队列元素为字符串型
queue<string> q;
如何设立访问标记,类比上一题走迷宫,使用二维数组通过位置坐标对应一个数值表示其层序来实现访问和标记操作
这里有一个对应索引关系,位置——层序,而这就是“键值对”的思想
本题中,是一个状态对应层序,状态——层序,其实抽象出来就是“结点——层序”,结点数是唯一的,每个节点对应一个层序
可重复。
那么如何建立结点和层序的索引关系:map给出了答案,这个类维护了一组pair类型的数据,即多个“键值对”
但是类比数组的索引,本题索引操作更多,故用哈希版本的map——unordered_map类,来建立结点和层序的索引关系
unordered_map<string, int> d;
*/
#include <iostream>
#include <algorithm>
#include <unordered_map>
//存储键值对 <key, value> 类型的元素,建立结点和层序的索引关系
//其中各个键值对键的值不允许重复,且该容器中存储的键值对是无序的。
#include <queue>
using namespace std;
int bfs(string start)
{
//构造队列
queue<string> q;//存储每一步的状态
//构造索引关系
unordered_map<string, int> d;//记录每一种状态的层序,不允许重复,保证所有状态都只出现一次
//初始化起点:访问、标记、入队
q.push(start);
d[start] = 0;//起点层序记为0,表示没有交换
/*
可以使用[]操作符来访问key值对应的value值。
*/
//扩展访问邻接点
string end = "12345678x";//终点
while (q.size())
{
//依次出队
string temp = q.front();//取出
q.pop();//记得弹出
//可以提前结束循环
if (temp == end)
return d[temp];
//扩展,访问所有邻接点
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int distance = d[temp];//暂存,此时的字符串后续因为交换会发生改变
int k = temp.find('x');//字符串中x的下标
int x = k / 3, y = k % 3;//二维数组转换
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(temp[a * 3 + b], temp[k]);//交换棋子,进入下一个状态
if (!d.count(temp)) //不走走过的路(空地):temp之前就是这种状态,或者前几步出现过这个状态
{ //那没必要重复,因为这样步数一定比上次多,这也是BFS为什么能找到最短路的关键
//不重复走路
d[temp] = distance + 1;//交换后的状态结点,层序+1,边搜边操作
q.push(temp); //后面就以它为起点
}
swap(temp[a * 3 + b], temp[k]);//恢复现场,访问本结点(状态)的另一种选择
}
}
}
//没变成最终状态,无解
return -1;
}
int main()
{
char s[2];//忽略空格读取和存储技巧
string start;
for (int i = 0; i < 9; i ++ )
{
cin >> s;//字符串中将不会保存空格
start += *s;
}
cout << bfs(start) << endl;
return 0;
}
改进:
#include <iostream>
#include <algorithm>
#include <unordered_map>
//存储键值对 <key, value> 类型的元素,建立结点和层序的索引关系
//其中各个键值对键的值不允许重复,且该容器中存储的键值对是无序的。
#include <queue>
using namespace std;
int bfs(string start)
{
//构造队列
queue<string> q;//存储每一步的状态
//构造索引关系
unordered_map<string, int> d;//记录每一种状态的层序,不允许重复,保证所有状态都只出现一次
//初始化起点:访问、标记、入队
q.push(start);
d[start] = 0;//起点层序记为0,表示没有交换
/*
可以使用[]操作符来访问key值对应的value值。
*/
//扩展访问邻接点
string end = "12345678x";//终点
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//在循环中影响速度
while (q.size())
{
//依次出队
string temp = q.front();//取出
q.pop();//记得弹出
//可以提前结束循环
if (temp == end)
return d[temp];
//扩展,访问所有邻接点
int distance = d[temp];//暂存,此时的字符串后续因为交换会发生改变
int k = temp.find('x');//字符串中x的下标
int x = k / 3, y = k % 3;//二维数组转换
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(temp[a * 3 + b], temp[k]);//交换棋子,进入下一个状态
if (!d.count(temp)) //不走走过的路(空地):temp之前就是这种状态,或者前几步出现过这个状态
{ //那没必要重复,因为这样步数一定比上次多,这也是BFS为什么能找到最短路的关键
//不重复走路
d[temp] = distance + 1;//交换后的状态结点,层序+1,边搜边操作
q.push(temp); //后面就以它为起点
}
swap(temp[a * 3 + b], temp[k]);//恢复现场,访问本结点(状态)的另一种选择
}
}
}
//没变成最终状态,无解
return -1;
}
int main()
{
char s[2];//忽略空格读取和存储技巧
string start;
for (int i = 0; i < 9; i ++ )
{
cin >> s;//字符串中将不会保存空格
start += *s;
}
cout << bfs(start) << endl;
return 0;
}