Breadth First Search(BFS)----广度优先搜索
基础知识:
广度优先搜索是一种分层的搜索过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有回退的情况。因此,广度优先搜索不是一个递归的过程,其算法也不是递归的。
实现过程简图:
应用范围:(目前已知)
连通块模型问题,最短路径或时间问题
例题1. POJ 1562 ---- Oil Deposits
Description
The GeoSurvComp geologic survey company is responsible for detecting underground oil deposits. GeoSurvComp works with one large rectangular region of land at a time, and creates a grid that divides the land into numerous square plots. It then analyzes each plot separately, using sensing equipment to determine whether or not the plot contains oil. A plot containing oil is called a pocket. If two pockets are adjacent, then they are part of the same oil deposit. Oil deposits can be quite large and may contain numerous pockets. Your job is to determine how many different oil deposits are contained in a grid.
Input
The input contains one or more grids. Each grid begins with a line containing m and n, the number of rows and columns in the grid, separated by a single space. If m = 0 it signals the end of the input; otherwise 1 <= m <= 100 and 1 <= n <= 100. Following this are m lines of n characters each (not counting the end-of-line characters). Each character corresponds to one plot, and is either *', representing the absence of oil, or
@’, representing an oil pocket.
Output
are adjacent horizontally, vertically, or diagonally. An oil deposit will not contain more than 100 pockets.
Sample Input
1 1
*
3 5
*@*@*
**@**
*@*@*
1 8
@@****@*
5 5
****@
*@@*@
*@**@
@@@*@
@@**@
0 0
Sample Output
0
1
2
2
典型的连通块模型,思路如下:
先一行一行地遍历每一个未被查找到(未被标记)的元素,若找到目标元素(@),则调用bfs函数搜索与其联通的区域,并标记这一区域内所有的目标元素。标记完成后接着遍历未被标记的元素,直到查找到最后一个元素。
代码:
#include <iostream>
#include <string.h>
#include <queue>
#include <algorithm>
using namespace std;
int visit[105][105];
char grid[105][105];
int n, m;
void bfs(int row , int column)
{
queue< pair<int,int> > q; // 利用队列进行遍历
q.push( make_pair(row,column) );
visit[row][column]=1; // 对当前点进行标记
while( !q.empty() ) // 若队列没有剩余元素,则退出循环
{ // 移动坐标 八个方向(这种写法省去了数组的设置)
for ( int x=-1 ; x<=1 ; x++ )
{ // horizontal
for ( int y=-1 ; y<=1 ; y++ )
{ // vertical
int tx=q.front().first+x;
int ty=q.front().second+y;
// 边界不是row和column...
// if ( tx>=1 && tx<=row && ty>=1 && ty<=column && visit[tx][ty]==0 && grid[tx][ty]=='@' )
if ( tx>=1 && tx<=n && ty>=1 && ty<=m && visit[tx][ty]==0 && grid[tx][ty]=='@' )
{ // 判断此位置是否在界内,且此位置是否被标记,且是否是'@'
visit[tx][ty]=1;
// 之后存入队列
q.push( make_pair( tx , ty ) );
}
}
}
q.pop(); // 八个方向都找完了,删除队列的首元素(即刚刚向八个方向发散的发散点),之后对后面的点进行相同操作
}
}
int main()
{
while ( cin>>n>>m && n && m )
{
memset(visit , 0 , sizeof visit);
memset(grid , 0 , sizeof grid);
int ans=0;
for (int r=1 ; r<=n ; r++ )
{
for (int c=1 ; c<=m ; c++ )
cin>>grid[r][c];
}
for (int r=1 ; r<=n ; r++ )
{ // 查找口袋(符号@)
for (int c=1 ; c<=m ; c++ )
{
if ( visit[r][c]==0 && grid[r][c]=='@' )
{ // 若找到未被查找过的'@'
bfs(r,c); // 查找周围有没有相邻的'@'
ans++; // 一整块都找完了,答案自增
}
}
}
cout<<ans<<endl;
}
return 0;
}
例题2. HDU 1072 ---- Nightmare(噩梦)
Problem Description
Ignatius had a nightmare last night. He found himself in a labyrinth with a time bomb on him. The labyrinth has an exit, Ignatius should get out of the labyrinth before the bomb explodes. The initial exploding time of the bomb is set to 6 minutes. To prevent the bomb from exploding by shake, Ignatius had to move slowly, that is to move from one area to the nearest area(that is, if Ignatius stands on (x,y) now, he could only on (x+1,y), (x-1,y), (x,y+1), or (x,y-1) in the next minute) takes him 1 minute. Some area in the labyrinth contains a Bomb-Reset-Equipment. They could reset the exploding time to 6 minutes.
Given the layout of the labyrinth and Ignatius’ start position, please tell Ignatius whether he could get out of the labyrinth, if he could, output the minimum time that he has to use to find the exit of the labyrinth, else output -1.
Here are some rules:
- We can assume the labyrinth is a 2 array.
- Each minute, Ignatius could only get to one of the nearest area, and he should not walk out of the border, of course he could not walk on a wall, too.
- If Ignatius get to the exit when the exploding time turns to 0, he can’t get out of the labyrinth.
- If Ignatius get to the area which contains Bomb-Rest-Equipment when the exploding time turns to 0, he can’t use the equipment to reset the bomb.
- A Bomb-Reset-Equipment can be used as many times as you wish, if it is needed, Ignatius can get to any areas in the labyrinth as many times as you wish.
- The time to reset the exploding time can be ignore, in other words, if Ignatius get to an area which contain Bomb-Rest-Equipment, and the exploding time is larger than 0, the exploding time would be reset to 6.
Input
The input contains several test cases. The first line of the input is a single integer T which is the number of test cases. T test cases follow.
Each test case starts with two integers N and M(1<=N,Mm=8) which indicate the size of the labyrinth. Then N lines follow, each line contains M integers. The array indicates the layout of the labyrinth.
There are five integers which indicate the different type of area in the labyrinth:
0: The area is a wall, Ignatius should not walk on it.
1: The area contains nothing, Ignatius can walk on it.
2: Ignatius’ start position, Ignatius starts his escape from this position.
3: The exit of the labyrinth, Ignatius’ target position.
4: The area contains a Bomb-Reset-Equipment, Ignatius can delay the exploding time by walking to these areas.
Output
For each test case, if Ignatius can get out of the labyrinth, you should output the minimum time he needs, else you should just output -1.
Sample Input
3
3 3
2 1 1
1 1 0
1 1 3
4 8
2 1 1 0 1 1 1 0
1 0 4 1 1 0 4 1
1 0 0 0 0 0 0 1
1 1 1 4 1 1 1 3
5 8
1 2 1 1 1 1 1 4
1 0 0 0 1 0 0 1
1 4 1 0 1 1 0 1
1 0 0 0 0 3 0 1
1 1 4 1 1 1 1 1
Sample Output
4
-1
13
这是一道最短路径问题,并且附加了一些条件。
思路:仍然利用队列工具,需要创建结构体,储存坐标、时间和路程。先记录起点,然后向四个方向一层一层地寻找可行路径。注意“生命包”(Bomb-Reset-Equipment)的处理(因为如果返回来在拿一次生命包,则一定会走重复的路线,所以要找最优路线,不能返回之前生命包的位置,于是就在之前生命包的位置放一堵墙)。若在同一层的搜寻中搜到了终点,那么就找到了最短路径。
代码:
#include <iostream>
#include <queue>
#include <string.h>
using namespace std;
/* 碰到死胡同(只能返回的情况)也不要紧,因为每一个点的
所有能够走通的下一个点,都将和时间、步数一起作为整体
储存在队列中,而这些整体早晚都会被使用,除非已经找到
了最优方案。
还要再熟悉熟悉bfs的过程
*/
/* 遇到这种算最短路径的题目,可以把时间和步数都记录在每
一个点上,即:建立结构体
*/
int n,m, ans, grid[10][10];
struct point
{
int x;
int y;
int time;
int step;
};
void bfs( int x , int y )
{
int next[4][4]={ {1,0},{0,1},{-1,0},{0,-1}};
queue<point> q;
point start={ x , y ,6 , 0};
q.push(start); // 把起点信息放入队列
while ( !q.empty() )
{ // 若队列中无任何元素,结束循环
// 进行循环时,上一个循环找到的点均已存入队列,则查找位于队首的点的所有相邻点
point tp=q.front();
q.pop(); // 赋给临时变量后,删除这个点
// 下面查找四个方向是否可行
for ( int i=0 ; i<4 ; i++ )
{
point t;
t.x=tp.x+next[i][0];
t.y=tp.y+next[i][1];
t.time=tp.time-1;
t.step=tp.step+1;
if ( t.x>0 && t.x<=n && t.y>0 && t.y<=m && grid[t.x][t.y]!=0 && t.time>0 )
{ /* 相比较于上一个连通块的题目,少了一个对已经走过的路径的标记,
因为这道题目可以重复走一个方格;同时增加了时间的判断*/
if ( grid[t.x][t.y]==3 )
{ // 到达终点
ans=t.step; // 步数直接赋给ans 最先找到的一定是最优解
// 因为每次找到同一批点,所用的步数是相同的
return;
}
if ( grid[t.x][t.y]==4 )
{ // 找到生命包,延长时间
t.time=6;
/* 为了寻找最优解,每个生命包只能拿一次。因为如果返回来在拿一次
生命包,则一定会走重复的路线*/
grid[t.x][t.y]=0; // 直接加一堵墙
}
// 把这个可行点包装好,放入队列
q.push(t);
}
}
}
ans=-1; // 若队列最终为空,且没有通过终点判断退出函数,则说明未找到逃生路线
}
int main()
{
int t; cin>>t;
while ( t-- )
{
cin>>n>>m;
int x,y;
ans=0;
for ( int r=1 ; r<=n ; r++ )
{ // 横纵坐标从1开始
for ( int c=1 ; c<=m ; c++ )
{
cin>>grid[r][c];
if ( grid[r][c]==2 )
// 记录起点
x=r,y=c;
}
}
bfs( x , y );
cout<<ans<<endl;
}
return 0;
}