[洛谷] P1379 八数码难题( 提高+/省选- )

1.题目

题目描述

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入格式

输入初始状态,一行九个数字,空格用0表示

输出格式

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)

样例输入 #1

283104765

样例输出 #1

4

2.分析

思路分析:
1.如何表示每次的状态?
用字符串表示
2.如何存储新状态?
哈希映射
3.此题本质?
bfs

3.代码

1. bfs (+queue) + unordered_map

#include <iostream>
using namespace std;

#include <queue>   //队列
#include <unordered_map>  //哈希表 
#include <algorithm>   //swap

string st;
int bfs()
{
	string ed = "123804765";
	queue<string> q;   //队列
	unordered_map<string, int> d; //距离初始状态的步数
	//init
	q.push(st);
	d[st] = 0;

	while (!q.empty())
	{
		//取出队头
		auto t = q.front();
		q.pop();

		//是否达到目标
		int dist = d[t];
		if (t == ed) return dist;

		//未达成目标,则找到'0',与四周进行交换
		int k = t.find('0');
		int x = k / 3, y = k % 3;

		int dx[] = { -1,1,0,0 }, dy[] = { 0,0,-1,1 };
		for (int i = 0; i < 4; ++i)
		{
			int a = x + dx[i], b = y + dy[i];
			if (a >= 0 && a < 3 && b >= 0 && b < 3)
			{
				swap(t[k], t[a * 3 + b]);
				if (!d.count(t))  //新状态
				{
					//插入队列
					q.push(t);
					//更新 新状态 的距离
					d[t] = dist + 1;
				}
				swap(t[k], t[a * 3 + b]);  //回溯
			}
		}
	}

	队列为空时,仍然无法移动成功
	//return -1;
}

int main()
{
	cin >> st;
	printf("%d", bfs());
	return 0;
}

在这里插入图片描述

重点分析

		int k = t.find('0');   //找到'0'所在的一维位置
		int x = k / 3, y = k % 3;  //转化为二维表示

2.双向bfs (适用于知道起始状态的情况)

思路分析

分别用两个队列存储从开头出发的点 ,和 , 从结尾出发遍历的点
当,遍历过同一个点的时候,说明已经找到了最小步数

#include <iostream>
using namespace std;

#include <queue>
#include <unordered_map>
#include <map>
string st;
string ed = "123804765";
//双端队列
int dbfs()
{
	queue<string> q1, q2;
	unordered_map<string, int> d1, d2;
	q1.push(st);
	q2.push(ed);
	d1[st] = 0;
	d2[ed] = 0;
	while (q1.size() && q2.size())
	{
		//从短的出发 !!!  [由于是要求最短步数,每次从短的出发]
		string t;
		if (q1.size() <= q2.size())
		{
			t = q1.front();
			q1.pop();

			int dist = d1[t];
			if (d2.count(t)) return dist + d2[t];

			int k = t.find('0');
			int x = k / 3, y = k % 3;

			int dx[] = { -1,1,0,0 }, dy[] = { 0,0,-1,1 };
			for (int i = 0; i < 4; ++i)
			{
				int a = x + dx[i], b = y + dy[i];
				if (a >= 0 && a < 3 && b >= 0 && b < 3)
				{
					swap(t[k], t[a * 3 + b]);
					if (!d1.count(t))
					{
						q1.push(t);
						d1[t] = dist + 1;
					}
					swap(t[k], t[a * 3 + b]);  //回溯
				}
			}
		}
		else
		{
			t = q2.front();
			q2.pop();

			//是否访问至同一个状态
			int dist = d2[t];
			if (d1.count(t)) return d1[t] + dist;

			int k = t.find('0');
			int x = k / 3, y = k % 3;
			int dx[] = { -1,1,0,0 }, dy[] = { 0,0,-1,1 };

			for (int i = 0; i < 4; ++i)
			{
				int a = x + dx[i], b = y + dy[i];
				if (a >= 0 && a < 3 && b >= 0 && b < 3)
				{
					swap(t[k], t[a * 3 + b]);
					if (!d2.count(t))
					{
						q2.push(t);
						d2[t] = dist + 1;
					}
					swap(t[k], t[a * 3 + b]);
				}
			}
		}
	}
	//无法通过变换得到
	return -1;
}

int main()
{
	cin >> st;
	printf("%d", dbfs());
	return 0;
}

在这里插入图片描述

3.双向bfs优化

思路

由于构造两个队列中有重复性代码
其实可以用一个队列,但是额外构造哈希映射
用 0 ,1 ,2分别表示未被遍历,被顺序遍历,被逆序遍历

补充:注意特判!!!!!

#include <iostream>
using namespace std;

#include <queue>
#include <unordered_map> 
#include <algorithm>

string st, ed = "123804765";
int dbfs()
{
	//特判
	if (st == ed) return 0;  
	queue<string> q;
	unordered_map<string, int> d, v;
	d[st] = 0;
	d[ed] = 1;
	v[st] = 1;  //顺序访问过
	v[ed] = 2; // 逆序访问过
	q.push(st);
	q.push(ed);
	while (!q.empty())
	{
		//取出队头
		string now, cur;
		now = cur = q.front();
		q.pop();

		int k = now.find('0');
		int x = k / 3, y = k % 3;
		int dx[] = { -1,1,0,0 }, dy[] = { 0,0,-1,1 };
		for (int i = 0; i < 4; ++i)
		{
			int a = x + dx[i], b = y + dy[i];
			if (a >= 0 && a < 3 && b >= 0 && b < 3)
			{
				swap(now[k], now[a * 3 + b]);

				//顺序/逆序访问过了
				if (v[now] == v[cur])
				{
					swap(now[k], now[a * 3 + b]);
					continue;
				}

				//顺序+逆序访问过了
				if (v[now] + v[cur] == 3) return d[now] + d[cur];

				//否则 说明是某个方向的延伸
				d[now] = d[cur] + 1;
				v[now] = v[cur];    //标记
				q.push(now);
				swap(now[k], now[a * 3 + b]);
			}
		}
	}
}

int main()
{
	cin >> st;
	printf("%d", dbfs());
	return 0;
}

在这里插入图片描述

4.总结

bfs的深入学习

5.更新日志

2022.9.18 整理

欢迎交流、讨论、指正~
不正确、不理解之处欢迎评论留言~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值