路径寻找问题可以转换为隐式图的广度遍历,目的是找到一条从初始状态到最终状态的最优路径,它不同于回溯法那样找到一个符合某些要求的解(如N皇后问题,数独问题等),回溯法是在深度遍历中进行剪枝。还要注意的是路径寻找问题是图的广度遍历不是树的广度遍历,树的广度遍历不需要判重,而图的广度遍历是需要判重的,如果不判重,时间和空间很有可能会产生浪费。
八数码问题、倒水问题都是路径寻找问题。下面给出解决八数码问题的代码。注意它和树的BFS的异同,也注意其中使用的小技巧,如memcmp()和memcpy()。
#include<cstdio>
#include<cstring>
#include<unordered_set>
using namespace std;
typedef int State[9];
const int maxstate = 1000000;
State st[maxstate], goal; //用st数组模拟队列,front为队首,rear为队尾,特殊情况可能会特别定义状态结构体并使用优先队列
int dist[maxstate]; //移动步数
const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, -1, 1};
unordered_set<int> vis; //C++11,底次使用hash_table,而不是像set使用rb-tree
void init_lookup_table() {
vis.clear();
}
int try_to_insert(int s) {
int v = 0;
for(int i = 0; i < 9; i++) v = v * 10 + st[s][i];
if(vis.count(v)) return 0;
vis.insert(v);
return 1;
}
int BFS() {
init_lookup_table(); //判重使用
int front = 1, rear = 2; //front表示步数,rear表示最后一个添加进去的状态所在的编号
while(front < rear) {
State& s = st[front];
if(memcmp(goal, s, sizeof(s)) == 0) return front; //找到目标
int z;
for(z = 0; z < 9; z++) if(!s[z]) break; //找到0的位置,也就是需要移动的格子(空白块)的位置
int x = z / 3, y = z % 3; //获取行列编号
for(int d = 0; d < 4; d++) {
int newx = x + dx[d];
int newy = y + dy[d];
int newz = newx * 3 + newy; //新的空白块的位置
if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3) { //如果移动合法
State& t = st[rear];
memcpy(&t, &s, sizeof(s)); //扩展新结点
t[newz] = s[z];
t[z] = s[newz];
dist[rear] = dist[front] + 1; //更新移动步数
if(try_to_insert(rear)) rear++;
}
}
front++; //更新移动步数
}
return 0;
}
int main() {
for(int i = 0; i < 9; i++) scanf("%d", &st[1][i]);
for(int i = 0; i < 9; i++) scanf("%d", &goal[i]);
int ans = BFS();
if(ans > 0) printf("%d\n", dist[ans]); //最少移动多少步
else printf("-1\n");
return 0;
}
/*
输入:
2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2
输出:
31
*/