【题目链接】
【题目考点】
1. 广度优先搜索
2. 状态压缩
3. set 红黑树
【解题思路】
9个数字构成的3乘3布局作为一个状态,每次移动数字后,布局发生改变。
从当前布局开始,一次移动后可以达到2到4种布局。再次移动后,又可以达到几种布局,所有的布局(状态)构成一个解空间树。要想求出从起始布局到目标布局的最少移动步数,需要使用广度优先搜索。当访问到的布局和目标布局相同时,返回走过的步数。
在广搜过程中,需要判断当前布局是否是先前已经出现过的布局,如果先前已经出现过,则不需要将该状态入队。
该题难点在于,如何记录一个布局(状态)是否出现过。
解法1:使用整数表示布局
可以将布局转为整数,而后使用set或unordered_set记录该状态是否已出现过。这是一种状态压缩的思想。
记(x,y)表示二维数组的x行y列元素。则每个布局共有9个位置:(1,1), (1,2), (1,3), (2,1), (2,2), (2,3), (3,1), (3,2), (3,3)。将每个位置的数字按顺序组合在一起,会构成一个9位整数,因此可以使用9位整数代替布局。
设set或unordered_set类型的vis,如果某布局已经出现,则将该布局转为整数加入vis。
判断某布局是否已出现,也可以看vis是否存在该布局对应的整数。
至于交换两个位置的数字,可以将整数转为二维数组后,将0与其上下左右四个位置的数字交换。
解法2:使用string类字符串表示布局
使用由9个数字字符组成的字符串表示布局。
设set或unordered_set类型的vis,来记录一个布局是否已出现过。
表示布局的字符串的下标为:0 1 2 3 4 5 6 7 8
其各下标在3乘3网格中的对应位置为:
0 1 2
3 4 5
6 7 8
查找得到字符0的下标为i,交换0和其周围数字
- 向上交换:只要i大于等于3,就让s[i]和s[i-3]交换
- 向下交换:只要i小于6,就让s[i]和s[i+3]交换
- 向左交换:只要i不是3的倍数,就让s[i]和s[i-1]交换
- 向右交换:只要i除以3的余数不为2,就让s[i]和s[i+1]交换。
【题解代码】
解法1:状态压缩,使用整数表示布局
#include<bits/stdc++.h>
using namespace std;
struct Node
{
int s, step;
};
set<int> vis;//vis.count(x) == 1,则数字串为x的局面出现过
int dir[4][2] = {{0,1},{0,-1},{1,0},{-1,0}};
int num[4][4], zx, zy;//num:数字布局 (zx, zy):0所在位置
void toArr(int a)//将整数转为二维数组num
{
for(int i = 3; i >= 1; --i)
for(int j = 3; j >= 1; --j)
{
num[i][j] = a%10;
if(num[i][j] == 0)
zx = i, zy = j;
a /= 10;
}
}
int toNum()
{
int n = 0;
for(int i = 1; i <= 3; ++i)
for(int j = 1; j <= 3; ++j)
n = n*10+num[i][j];
return n;
}
int bfs(int s)
{
queue<Node> que;
vis.insert(s);
que.push(Node{s, 0});
while(que.empty() == false)
{
Node u = que.front();
que.pop();
if(u.s == 123804765)
return u.step;
toArr(u.s);
for(int i = 0; i < 4; ++i)
{
int x = zx+dir[i][0], y = zy+dir[i][1], n;
if(x >= 1 && x <= 3 && y >= 1 && y <= 3)
{
swap(num[zx][zy], num[x][y]);
n = toNum();
if(vis.count(n) == 0)
{
vis.insert(n);
que.push(Node{n, u.step+1});
}
swap(num[zx][zy], num[x][y]);
}
}
}
}
int main()
{
int s;
cin >> s;
cout << bfs(s);
return 0;
}
解法2:使用string类字符串表示布局
#include<bits/stdc++.h>
using namespace std;
struct Node
{
string s;
int step;
};
set<string> vis;
string change(string s, int d)//d:方向:0,1,2,3表示上下左右
{
int i = s.find('0');
if(d == 0 && i >= 3)//上
swap(s[i], s[i-3]);
else if(d == 1 && i < 6)//下
swap(s[i], s[i+3]);
else if(d == 2 && i%3 != 0)//左
swap(s[i], s[i-1]);
else if(d == 3 && i%3 != 2)//右
swap(s[i], s[i+1]);
return s;
}
int bfs(string s)
{
queue<Node> que;
que.push(Node{s, 0});
vis.insert(s);
while(!que.empty())
{
Node nd = que.front();
que.pop();
if(nd.s == "123804765")
return nd.step;
for(int d = 0; d < 4; ++d)
{
string t = change(nd.s, d);
if(vis.count(t) == 0)
{
que.push(Node{t, nd.step+1});
vis.insert(t);
}
}
}
return -1;
}
int main()
{
string s;
cin >> s;
cout << bfs(s);
return 0;
}