BFS——《算法笔记》8.2小节 问题 C: 【宽搜入门】8数码难题

问题 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:种一棵树最好是十年前,其次是现在

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值