问题 C: 【宽搜入门】8数码难题
时间限制: 20 Sec 内存限制: 128 MB
提交: 176 解决: 60
[提交][状态][讨论版][命题人:外部导入]
题目描述
初始状态的步数就算1,哈哈
输入:第一个3*3的矩阵是原始状态,第二个3*3的矩阵是目标状态。
输出:移动所用最少的步数
Input
2 8 3
1 6 4
7 0 5
1 2 3
8 0 4
7 6 5
Output
6
题解:
看了网上挺多文章,自己也琢磨挺久的,终于在深夜搞定了。
关键思想:用map实现映射
参考别人的博客:
一、状态如何表示?
1.每个状态都用3*3的数组表示,但是BFS中需要入队出队,比较麻烦而且空间占用较大
2.状态压缩,采用一个整数保存状态的数字序列,例如状态1表示为283104765,状态2表示为203184765
二、如何判重?
1.如果空间允许,开一个876543210大小的bool数组,某个序列出现就将数组值置为1;但是竞赛中一般都是限制128M(大约10000000),这个数组开不下来。
2.虽然状态范围是012345678--876543210,但是中间真正有效的只有9!=362800,因为数字不可能出现重复;因此可以考虑开一个数组大小为9!整型数组A和bool数组B,然后生成0-8这9个数码的全排列并按照升序或者降序存入数组中,要判断某个状态(一种排列方式)是否出现过,直接通过二分查找的方式找到该排列在A中的下标i,然后查看数组B[i]为true还是false;如果为true则出现过,如果为false则将状态入队,并设置B[i]=true;
3.其实从方案2中我们已经看到,判重的实质就是建立状态数字串(一个int数据)和是否出现(一个bool数据)之间的联系,而STL中刚好提供了map<key,value>这样一种容器,我们可以将状态数字串作为key,是否出现作为value直接建立起状态--是否出现的联系。
4.使用hash判重,将状态数字串通过某种映射f(x)从012345678--876543210这样一个大集合,映射到128M范围之内;这里采用简单的hash,取模一个大质数,只要这个质数大于9!即可;当然这里可能出现冲突,也就是key1!=key2但是f(key1)==f(key2),hash算法只能减少冲突不能避免冲突。这里如何减少冲突呢?挂链表,当key1!=key2但是f(key1)==f(key2),则将key2挂到key1后面;当然这里如果使用康托展开可以完美一一映射而不冲突,但是我不会(~^~)。
我选择的方案三
AC代码:
#include<iostream>
#include<queue>
#include<map>
#include<algorithm>
using namespace std;
struct node
{
int a[3][3];
int x0, y0; //记录0的位置
int step; //记录步数
};
map <int, bool> M; //用map实现映射
int my_hash(int t[3][3]) //映射/标志
{
int p = 0;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
p = p * 10 + t[i][j];
}
}
return p;
}
bool next_sta(node &t, int n) //交换
{
int x = t.x0, y = t.y0;
if (n == 0 && x > 0) //上
{
swap(t.a[x][y], t.a[x - 1][y]);
t.x0 = t.x0 - 1;
return true;
}
if (n == 1 && x <2) //下
{
swap(t.a[x][y], t.a[x + 1][y]);
t.x0 = t.x0 + 1;
return true;
}
if (n == 2 && y>0) //左
{
swap(t.a[x][y], t.a[x][y-1]);
t.y0 = t.y0 - 1;
return true;
}
if (n == 3 && y<2) //右
{
swap(t.a[x][y], t.a[x][y + 1]);
t.y0 = t.y0 + 1;
return true;
}
return false;
}
int main()
{
node start, end;
for (int i = 0; i < 3; i++) //输出初始状态
{
for (int j = 0; j < 3; j++)
{
cin >> start.a[i][j];
if (start.a[i][j] == 0)
{
start.x0 = i;
start.y0 = j;
}
}
}
for (int i = 0; i < 3; i++) //目标状态
{
for (int j = 0; j < 3; j++)
cin >> end.a[i][j];
}
queue <node>Q;
start.step =1, end.step = 0;
Q.push(start);
M[my_hash(start.a)] = 1;
int flag = 0;
while (!Q.empty())
{
node vn = Q.front();
Q.pop();
for (int i = 0; i < 4; i++) //0,1,2,3分别代表上下左右
{
node temp = vn;
if (next_sta(temp, i)) //判断该交换是否可行
{
if (M[my_hash(temp.a)] == 0) //该状态未出现过
{
M[my_hash(temp.a)] = 1;
temp.step++;
if (my_hash(temp.a) == my_hash(end.a)) //与最终结点的值相同
{
end.step = temp.step;
flag = 1;
break;
}
Q.push(temp);
}
}
}
if (flag)break;
}
cout << end.step << endl;
return 0;
}
AC截图:
PS:种一棵树最好是十年前,其次是现在