洛谷 P1379 八数码难题

【题目链接】

洛谷 P1379 八数码难题

【题目考点】

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值