在搜索算法中,最为简单的并且最为重要的要数BFS(宽度优先搜素),DFS(深度优先搜索)两种算法。搜索领域中的高深算法,都是以这两种算法为基础!灵活运用这两种算法,毫不夸张来说,可以解决编程中的任何问题(当然,如果忽略时间限制的话)。今天主要谈谈BFS在走迷宫问题的应用。
著名的沃斯科学家提过这样的公式 程序=算法+数据结构(顺便一提,对于软件工程领域有这样的公式 软件=程序+文档)。对应于BFS算法的数据结构,一般是一个队列(顺便提一下DFS对应的是栈)。
现在有这样的一个迷宫,S表示起始位置,E表示结束位置,在迷宫中有一些障碍‘*’,不能走障碍位置。那么问最少需要多少步可以从起始位置走到重点位置(当然,愿意的话,完全可以输出所走的路径,这些都是小问题)
首先,什么是BFS?百度百科的定义是:
BFS,其英文全称是Breadth First Search。 BFS并不使用经验法则算法。从算法的观点,所有因为展开节点而得到的子节点都会被加进一个先进先出的队列中......
我提一些个人的看法(为便于理解,有些地方并不准确),先上一张图。
闪电是如何扩散的?我们发现闪电先是一道主光柱,然后由主光柱再扩展出许多分支,那么是否能联想到离散里的树?以下是一棵树:
BFS搜索的话就是先对行1的节点1进行搜索,看它是否符合条件,否则继续搜索行1的节点1的所有子节点,即行2的所有节点,看是否有节点符合条件,如果没有则依次以行2的节点为根继续往下搜索行3的子节点,如此重复直到搜索到目标节点或者将所有节点搜索完为止。以上是BFS最简单的搜索思想。接下来我们将这一思想具体到一个迷宫中。
公主晕倒在了火海之中,王子准备前往营救(╯▽╰),但是王子不能越过途中的火海,且王子一次只能选择上下左右四个方向的任一方向前进一格。那么你可以有很多途径到达终点,但是如何用算法求得所需行走的最短次数呢?我以为需要注意以下几点:
- 创建一个能进行宽搜的节点待查队列。
- 标记那些已经搜索过的节点,以便下次不会重复搜索。
- 记录每个节点与根节点(起始点)的距离。
综上,我们算法要做的就是以起始点(王子最初所在点)为中心点,设定该中心点到起始点距离为0,检测起始点是否就是终点,如果不是,向上下左右四个方向搜索与起始点相邻的节点同时将其标记为已搜索,并将符合条件的节点加入到待查队列中,记录这些子节点与起始点的距离。之后再以待查队列的中任一节点为中心点(具体为哪个节点,这要看刚刚是按何种顺序将子节点添加到待查队列当中),检测它是否就是终点,如果不是,继续往当前中心点的四周搜索并标记为已搜索,添加符合条件的节点进入待查列表,并记录这些子节点与起始点的距离。如此循环直到检测到当前中心点为终点为止。然后输出记录的距离。
个人比较形象的感觉就是,这有点像波的扩散过程,每个子波都产生新的波面。而每个子节点都产生新的子节点集。(这种说法仅仅为了便于理解,但是并不准确)
大致步骤如下:
- 将起始节点加入队列queue[0]中,并标记为已搜索,以起始点为中心点,将该点距离设为0。
- 进行出队列操作,判断当前点是否为终点,如果是,终止算法,如果不是进行步骤3。
- 搜索中心点上下左右四个方向的相邻节点,并标记为已搜索。(如果不进行标记,那么会有什么后果?明显,会对任意一个点进行无休止的搜索)
- 将符合条件的节点添加到待查队列中,记录节点与起始点的距离。
5,循环步骤2~5。
算法分析至此,接下来是代码时间。程序猿们请准备好你们的武器。
我们先做些约定:
- 以’S’表示起始点,以’E’表示终点
- ‘.’表示路径可通过,’*’表示障碍
#include <iostream>
#include <stdio.h>
#define N 100
/*******************
N用于确定地图矩阵的最大行或列
*******************/
using namespace std;
struct queue
{
char c;
int x, y, dis;
};
/*******************
定义一个结构类型,x,y指示坐标位置,dis指示节点到起始点的距离
c用于存储迷宫字符
*******************/
int main()
{
struct queue que[N*N];
char maze[N][N] = {0};
int r, c, sx, sy; //r指示迷宫矩阵的行数,c指示迷宫矩阵的列数,sx,sy用于存储起始点的坐标
cin >> r >> c;
for(int i = 0; i < r; i++)
{
for(int j = 0; j < c; j++)
{
cin >> maze[i][j]; //先从屏幕上读取迷宫矩阵
if(maze[i][j] == 'S') //起始点
{
sy = i;
sx = j;
}
}
}
int vis[N][N] = {0}; //用于记录是否搜索过某一节点,为0表示没被搜索过,为1表示已搜索过
int dx[] = {1, 0, 0, -1};
int dy[] = {0, 1, -1, 0};
/******************
dx[],dy[]数组比较关键,通过这两个数组可以实现对中心点四周的节点的搜索
******************/
int front = -1, rear = -1;
que[++rear].c = maze[sy][sx];
que[rear].x = sx;
que[rear].y = sy;
que[rear].dis = 0;
vis[sx][sy] = 1;
/**********************
这一块实现上述的步骤1
**********************/
while(front < rear)
{
++front;
if(que[front].c == 'E')
{
cout << que[front].dis << endl;
return 0;
}
else
{
for(int i = 0; i < 4; i++)
{
int tx = que[front].x + dx[i];
int ty = que[front].y + dy[i];
if(tx >= 0 && tx < c && ty >= 0 && ty < r
&& !vis[ty][tx] && maze[ty][tx] != '*')
/**********************
此处保证了搜索节点一定位于迷宫矩阵内同时目标节点不是碍,
且目标节点未被搜索过
**********************/
{
que[++rear].c = maze[ty][tx];
que[rear].x = tx;
que[rear].y = ty;
que[rear].dis = que[front].dis + 1;
vis[ty][tx] = 1;
}
}
}
}
cout << "you can't pass.\n";
return 0;
}
以下是我测试过的两个简单的输入与输出:
输入:
2 5
….E
S….
输出:5
输入:
2 5
..*.E
S.*..
输出:you can't pass.