先看题:
小 C 做了一个拼图游戏,大家来破解它吧。
游戏规则:每次可以移动相邻的两张图片,所有图片都在指定的位置上,游戏完成。
简化问题,每次输入一个 3 × 3 3\times 3 3×3 的矩阵,表示要拼的图。
分析
可以发现此题搜索树特别庞大,所以不进行状态判重是不行的。
所以我们可以开一个
9
9
9 维的 bool
数组来进行判重,但这样子空间复杂度约为 1GB,如果你家电脑能开的下也行。
很明显如果只对一个状态判重,那多开的其他空间就是浪费,所以考虑将一个 3 × 3 3\times 3 3×3 的矩阵压成一个 9 9 9 位十进制整数,如将矩阵:
123
456
789
压成
123456789
123456789
123456789,但是普通的数组开不到
1
0
9
10^9
109 的所以可以用 map
或哈希实现。
而广搜时只需将整数化为矩阵进行操作,再将矩阵压成整数放入队列。
代码
实现时有些技巧,具体看代码。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 15, mod = 1000003;//哈希模数设为质数效率会高些
int dx[] = {0, 1, 0}, dy[] = {0, 0, 1};
vector <int> nbr[mod + 5];//哈希数组
bool find(int x){//查找有没有出现过
int P = x % mod;
for(int i = 0; i < nbr[P].size(); i ++){
if(nbr[P][i] == x){
return true;
}
}
return false;
}
void insert(int x){//将 x 插入散列表
int P = x % mod;
nbr[P].push_back(x);
}
int get_now(int a[N][N]){//得到当前矩阵对应的整数
int tmp = 0;
for(int i = 1; i <= 3; i ++){
for(int j = 1; j <= 3; j ++){
tmp = tmp * 10 + a[i][j];
}
}
return tmp;
}
signed main(){
int t = 0;
for(int i = 1; i <= 3; i ++){
for(int j = 1; j <= 3; j ++){
char c;
cin >> c;
t = t * 10 + (c - '0' + 1);//将输入的矩阵压成整数
}
}
if(t == 123456789){
cout << "0";
return 0;
}
queue < pair <int, int> > q;
insert(t);
q.push(make_pair(t, 0));
while(!q.empty()){
int cur = q.front().first, step = q.front().second;
q.pop();
if(cur == 12345689){//判断是否达到目标状态
cout << step;
return 0;
}
int a[N][N];
for(int i = 3; i >= 1; i --){//将整数化为矩阵
for(int j = 3; j >= 1; j --){
a[i][j] = cur % 10;
cur /= 10;
}
}
for(int i = 1; i <= 3; i ++){
for(int j = 1; j <= 3; j ++){
for(int k = 1; k <= 2; k ++){//一个点只需和下、左方位的点互换就行
int nx = i + dx[k], ny = j + dy[k];
if(nx < 1 || nx > 3 || ny < 1 || ny > 3){
continue;
}
swap(a[i][j], a[nx][ny]);//交换位置
int o = get_now(a);//将矩阵化为整数
if(!find(o)){//状态判重
q.push(make_pair(o, step + 1));
insert(o);
if(o == 123456789){//由于搜索树很大在这里提前输出可以减少超多时间
cout << step + 1;
return 0;
}
}
swap(a[i][j], a[nx][ny]);//将矩阵恢复原样
}
}
}
}
return 0;
}