BFS算法是一种图遍历算法,它从起点开始,逐层扩展搜索范围,直到找到目标节点为止。
BFS算法一般选择队列作为节点存储的数据结构,我们将搜索目标节点的问题抽象为寻找目标状态,那么队列存储的对象就是每一种状态。
对于状态的含义与变化过程,BFS算法如下要求(为了讲解得更加透彻,举走迷宫问题为例):
八数码,在3×3的方格棋盘上,摆放着1到8这八个数码,有1个方格放置字符x,其初始状态如图所示,要求对字符x执行x左移、x右移、x上移和x下移这四个操作使得棋盘从初始状态到目标状态。
基于上述状态能够拓展出如下四种状态:
八数码问题要求是,对字符x进行若干次唯一操作,得到目标状态并且求算操作次数:
好啦,大功告成~~其他细节部分见代码后,也许会有更加深刻的体会(代码有详细注释,所谓优秀的代码本身就是学习文档!!!)
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
实在不好意思,上述程序由于状态的存储与状态的拓展过于繁杂,所以导致了Time Limit Exceeded了,如下提供优化状态存储版本的程序如下:
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
using namespace std;
/*
改进的地方:
1.每一种状态的步数通过unorder_map实现映射
减少了存储状态的内存消耗
2.采用string的find()函数查找,x的位置;
3.通过运算直接获取原状态可以拓展的状态
*/
string start; // 其实状态
string End = "12345678x"; // 最终状态
int bfs() {
queueq; // 存储状态
unordered_mapd; // d.count(t)表示t进入容器的次数
// 初始化
q.push(start);
d[start] = 0;
while (q.size()) {
auto t = q.front();
q.pop();
int pos = t.find('x'); // 查找x的位置
int distance = d[t];
int dx[4] = { -1,0,1,0 }, dy[4] = { 0,1,0,-1 }; // 拓展的位移
if (t == End) {
return d[t];
}
for (int i = 0;i < 4;i++) {
int a = pos / 3 + dx[i];
int b = pos % 3 + dy[i];
if (a >= 0 && a <= 2 && b >= 0 && b <= 2) {
swap(t[pos], t[3 * a + b]);
// 如果不是第一次加入队列
if (!d.count(t)) {
q.push(t);
d[t] = distance + 1;
}
swap(t[pos], t[3 * a + b]);
}
}
}
return -1; // 没有达到最终状态
}
int main() {
int cnt = 9;
while (cnt--) {
char a;
cin >> a;
start += a;
}
cout << bfs() << endl;
return 0;
}
本博客先解释了BFS的存储队列节点的抽象含义,将每一个节点看作一种状态,并从状态存储、状态记录、状态拓展等角度解答了BFS算法如何解决遍历问题。
紧接着,我们借助BFS算法的状态处理方法给出了八数码问题的求解思路。我们以字符串存储了八数码的每一种状态,每一种状态记录{s,pos,step}三个分量,借助st映射记录新拓展出的状态,并于代码中给出状态拓展的方法。
实际上,八数码问题只是抽象BFS问题的一种实例化,当我们判断一个问题是否属于BFS问题时,需要判断问题是否存在初始状态与最终状态?每一种状态如何存储,有哪些分量?如何记录每一种状态?每一种状态拓展出其他状态的方式是否是规律的且有限的?掌握了如上的思考方式,相信你能够在下一次遇到或判断一个问题是否属于BFS问题时,你能够更加游刃有余!!!