宽度优先搜索也是搜索的手段之一。它与深度优先搜索类似,从某个状态出发探索所有可以到达的状态。
与深度优先搜索的不用之处在于搜索的顺序,宽度优先搜索总是先搜索距离初始状态近的状态。也就是说,它按照开始状态---只需1次转移就可以到达的所有状态---只需2次转移就可以到达的所有状态---。。。这样的顺序进行搜索。对于同一个状态,宽度优先搜索只经过一次,因此复杂度为O(状态数*转移的方式)。
深度优先搜索(隐式的)利用了栈进行计算,而宽度优先搜索则利用了队列。搜索时首先将初始状态加到队列里,此后队列的最前端不断取出状态,把从该状态可以转移到的状态中尚未访问过的部分加入队列,
如此反复,直至队列被取空或找到了问题的解。通过观察这个队列,我们可以知道所有的状态都是按照距离初始状态由近及远的顺序遍历的。
迷宫的最短路径:给定一个大小为N*M迷宫。迷宫由通道和墙壁组成,每一步可以向邻接的上下左右四格的通道移动。请求出起点到终点所需要的最小步数。请注意,本题假定从起点一定可以移动到终点。N,M<=100
输入:'#','.','S','G'分别表示墙壁、通道、起点、终点
宽度优先搜搜按照距开始状态由近及远的顺序进行搜索,因此可以很容易的用来求最短路径、最少操作之类问题的答案。这个问题中,状态仅仅是目前所在位置的坐标,因此可以构成pair或者编码成int来表达状态。当状态更加复杂时,就需要封装成一个类来表示状态了。转移的方式为四方向移动,状态数与迷宫的大小是相等的,所以复杂度是O(4*N*M) = O(N*M)。
宽度优先搜索中,只需要将已经访问过的状态用标记管理起来,就可以很好地做到由近及远的搜索。这个问题中由于要求最短距离,不妨用d[N][M]数组把最短距离保存起来。初始时用充分大的常数INF来初始化它,这样尚未到达的位置就是INF,也就同时起到了标记的作用。
虽然到达终点时候就会停止搜索,可如果继续下去直到队列为空的话,就可以计算出到各个位置的最短距离。此外,如果搜索到最后,d仍然为INF的话,便可得知这个位置就是无法从起点到达的位置。
const int INF = 100000000;
//使用pair表示状态时,使用typedef会更加方便一些
typedef pair <int, int> P;
char maze[MAX_N][MAX_M+1]; //表示迷宫的字符串数组
int N, M;
int sx, sy; //起点坐标
int gx, gy; //终点坐标
int d[MAX_N][MAX_M]; //到达各个位置的最短距离的数组
//4个方向移动的向量
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
//求从{sx, sy} 到{gx, gy}的最短距离
//如果无法到达,则是INF
int bfs() {
queue<p> que;
//把所有的位置都初始化INF
for (int i = 0; i<N; i++)
for (int j = 0; j<M; j++) d[i][j] = INF;
//将起点加入队列,并把这一地点的距离设置为0
que.push(P(sx, xy));
d[sx][xy] = 0;
//不断循环直到队列的长度为0
while (que.size()) {
//从队列的最前端取出元素
P p = que.front(); que.pop();
//如果取出的状态已经是终点,则结束搜索
if (p.first == gx && p.second == gy) break;
//四个方向的循环
for (int i = 0; i<4; i++) {
//移动之后的位置记为(nx, ny)
int nx = p.first + dx[i], ny = p.second + dy[i];
//判断是否可以移动以及是否已经访问过(d[nx][ny] != INF即为已经访问过)
if (0 <= nx && nx < N && 0 <= ny && ny < M && maze[nx][ny] != '#' && d[nx][ny] == INF) {
//可以移动的话,则加入到队列,并且到该位置的距离确定为到p的距离加1
que.push(P(nx, ny));
d[nx][ny] = d[p.first][p.second] + 1;
}
}
}
return d[gx][gy];
}
void solve() {
int res = bfs();
cout << res << endl;
}