算法笔记练习 题解合集
题目
题目描述
初始状态的步数就算1,哈哈
输入:第一个33的矩阵是原始状态,第二个33的矩阵是目标状态。
输出:移动所用最少的步数
Input
2 8 3
1 6 4
7 0 5
1 2 3
8 0 4
7 6 5
Output
6
思路
0. 如何表示当前状态?
因为 3×3 的矩阵很小,就不用二维数组了,用一个一维数组即可:
1 2 3
4 5 6
7 8 9
如上所示,用下标 1 ~ 9 来表示 9 个位置上的数。
例如对于以下这样的状态,用一维数组来表示就是{1, 0, 2, 3, 4, 5, 6, 7, 8}
1 0 2
3 4 5
6 7 8
1. 下一个合法的状态是什么?
解决了表示的问题,对于 BFS 来说最重要的就是合法状态的推导。例如对于下方左侧假设的情况,0 表示空着的位置,那么 0 附近的 1、2、4 都可以移动到 0 的位置。
从一维数组的变化来看,其实就是 0 附近的数字都可以与其交换,例如和 4 交换就得到右边的情况。
1 0 2 1 4 2
3 4 5 -> 3 0 5
6 7 8 6 7 8
因此可以根据 0 交换(移动)的方位来分类讨论:
- 0 和其左侧数字交换:当且仅当 0 的下标
zero
满足zero % 3 != 1
时不会越界,同时与其交换的下标是zero - 1
; - 0 和其上侧数字交换:当且仅当 0 的下标
zero
满足zero > 3
是不会越界,同时与其交换的下标是zero - 3
; - ……
右侧和下侧的情况非常相似,具体实现见代码。
2. 如何保存目前的移动次数?
map<vector<int>, int> moveCnt;
个人喜欢用映射实现,因为映射既可以做到继承队首节点的移动次数,又可以顺便实现查重功能(已经出现过的状态不能再次出现,否则会无限循环)。具体见代码。
代码
#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
using namespace std;
vector<int> beg(10), fin(10);
map<vector<int>, int> moveCnt;
void BFS() {
moveCnt[beg] = 1;
queue<vector<int> > Q;
Q.push(beg);
while (!Q.empty()) {
vector<int> top = Q.front();
int nowCnt = moveCnt[top], zero;
if (top == fin) {
printf("%d\n", nowCnt);
return;
}
Q.pop();
for (zero = 1; zero <= 9; ++zero)
if (top[zero] == 0)
break;
if (zero > 3) {
swap(top[zero], top[zero - 3]);
if (!moveCnt.count(top)) {
moveCnt[top] = nowCnt + 1;
Q.push(top);
}
swap(top[zero], top[zero - 3]);
}
if (zero < 7) {
swap(top[zero], top[zero + 3]);
if (!moveCnt.count(top)) {
moveCnt[top] = nowCnt + 1;
Q.push(top);
}
swap(top[zero], top[zero + 3]);
}
if (zero % 3 != 1) {
swap(top[zero], top[zero - 1]);
if (!moveCnt.count(top)) {
moveCnt[top] = nowCnt + 1;
Q.push(top);
}
swap(top[zero], top[zero - 1]);
}
if (zero % 3 != 0) {
swap(top[zero], top[zero + 1]);
if (!moveCnt.count(top)) {
moveCnt[top] = nowCnt + 1;
Q.push(top);
}
swap(top[zero], top[zero + 1]);
}
}
}
int main() {
for (int i = 1; i <= 9; ++i)
scanf("%d", &beg[i]);
for (int i = 1; i <= 9; ++i)
scanf("%d", &fin[i]);
BFS();
return 0;
}