广度优先搜索遍历又名BFS,属于盲目搜寻法,是图的搜索算法之一,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。下面列出几道经典例题,来带领大家一同求解。
(1)给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。两个相邻元素间的距离为 1 。
本题意思是求解数组中每个数到数字0的距离,0自己本身的距离为0,且距离只考虑上下左右四个方向,不考虑左上,左下,右上,右下等方向。由于题目求各个点到0的距离,我们可以将0这个点入队列,然后从0这个点向上下左右四个方向扩散,并且标记是否访问过,可以直接修改原数组的值来进行标记,每个点都入队一次,下面贴出代码,注释也如下:
class Solution {
public:
vector < vector < int > >updateMatrix(vector< vector< int > > & mat) {
//定义上下左右四个方向的坐标,来方便结点进行移动访问
int dirs[4][2] = { {0,1}, {0,-1}, {1,0}, {-1,0}};
//数组的行数和列数
int m = mat.size(), n = mat[0].size();
//定义结果数组,分别将其中元素初始值都设置为0
vector < vector < int > >res(m,vector<int>(n));
//队列
queue< pair< int,int > > q;
//遍历数组中的每个元素
for(int i = 0;i < m;i++)
{
for(int j = 0;j < n;j++)
{
//将数组元素为0的加入到队列中
if(mat[i][j] == 0)
{
q.push( {i,j} );
}
}
}
//当队列不为空时
while(!q.empty())
{
//分别取出队头结点的坐标值
int x = q.front().first,y = q.front().second;
//队头出队
q.pop();
//分别寻找刚取出头结点的上下左右四个方向看是否有值为1的结点
for(int i = 0;i < 4;i++)
{
int nx = x + dirs[i][0],ny = y + dirs[i][1];
//如果坐标取值合理并且找到的值为1且结果数组未曾访问过
if(nx >= 0 && nx < m && ny>= 0 && ny <n && mat[nx][ny] ==1&&res[nx][ny]==0)
{
//新结点的坐标值为旧结点的坐标值+1,意味着距离更远一格
res[nx][ny] = res[x][y] + 1;
//将新结点入队,并且继续遍历寻找
q.push( {nx,ny} );
}
}
}
return res;
}
};
(2)有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。返回矩阵中 省份 的数量。
本题的意思即求无向图中连通域的个数,我们可以对图进行广度优先搜索来遍历图中的每个节点。依次遍历每个城市,如果该城市未被访问,则对该城市附近开始搜索,直到邻近的所有城市都被访问到,即得到一个连通分量,就是一个省份。 下面贴出代码:
class Solution {
public:
int findCircleNum(vector < vector < int > > & isConnected) {
//记录二维数组的行数(二维数组可以看作是一个方阵,其中长宽都相等)
int size = isConnected.size();
//定义一个数组来记录城市是否被访问
vector < int > temp(size);
//记录省份数量
int res = 0;
//创建一个队列
queue < int > q;
//访问每一个城市
for(int i=0;i<size;++i)
{
//该城市未被访问过
if( ! temp[i])
{
//为访问过的城市入队
q.push(i);
//队列不为空时
while( ! q.empty())
{
//取队头结点
int j = q.front();
//将访问数组的值更改,标记为此城市已被访问
temp[j] = 1;
//队头结点出队列
q.pop();
//找该城市相连的其他城市
for(int k = 0;k < size; ++k)
{
//如果新旧城市相连且新城市未被访问过,则入队列
if(isConnected[j][k] == 1 && ! temp[k])
{
q.push(k);
}
}
}
//当一个城市找完了与其相连的所有城市,即构成了连通分量,结果值加1
++res;
}
}
return res;
}
};
(3)给定一个包含了一些 0 和 1 的非空二维数组 grid 。一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 )
由题意知,让我们求出二维数组中以1构成的连通分量的最大值。我们可以先遍历二维数组中的每一个值,当值为1时我们将其加入队列,为0则跳过。依次对每个1的结点进行广度优先搜索遍历,找到与其相连的1的个数,直到遍历完所有的点,取出最大的连通分量个数即可。下面贴出代码:
class Solution {
public:
int maxAreaOfIsland(vector < vector < int > > & grid) {
//定义结点移动的四个方向,分别是上下左右
int dirs[4][2] = { {0,1}, {0,-1}, {1,0}, {-1,0}};
//定义数组的行数和列数
int m = grid.size();
int n = grid[0].size();
//最大的面积
int res = 0;
//定义队列
queue < pair < int,int > > q;
//遍历数组中的每一个元素
for(int i = 0; i < m; ++i)
{
for(int j = 0; j < n; ++j)
{
//若结点为0则退出此次遍历
if(grid[i][j] == 0)
{
continue;
}
//若元素值为1
else
{
//当前岛屿的面积,因为一开始有一个元素,所以面积定义为1
int temp = 1;
//将当前结点入队
q.push( {i,j} );
//更新结点值,表示该结点已访问
grid[i][j] = 0;
//队列不空时遍历
while( !q.empty())
{
//取出队头结点的坐标
int x = q.front().first,y = q.front().second;
//队头出队
q.pop();
//遍历该结点的上下左右四个方向
for(int k = 0;k < 4; ++k)
{
int mx = x+dirs[k][0];
int my = y+dirs[k][1];
//若新结点坐标合理,且新结点的值为1
if(mx >= 0 && mx < m && my >= 0 &&my<n &&grid[mx][my] == 1)
{
//当前岛屿的面积+1
++temp;
//将新结点入队
q.push( {mx,my} );
//将新结点标记为访问过
grid[mx][my] = 0;
}
}
}
//不断更新最大岛屿面积
res = max(res,temp);
}
}
}
return res;
}
};
(3) 在给定的网格中,每个单元格可以有以下三个值之一:值 0 代表空单元格;
值 1 代表新鲜橘子;值 2 代表腐烂的橘子。每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。
由题知:每个腐烂橘子会让周围上下左右的位置上的新鲜橘子腐烂,这就是个广度优先搜索遍历的算法。最后结果要求返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。实际就是腐烂橘子到新鲜橘子的最短路径。本体使用BFS来解决,代码如下:
class Solution {
public:
//分别定义元素要移动的四个方向,上下左右
int dirs[4][2] = { {1,0} , {-1,0} , {0,1} , {0,-1}};
int orangesRotting(vector < vector < int > > & grid)
{
//定义一个队列
queue < pair < int,int > > q;
//求出数组的行数列数
int m = grid.size();
int n = grid[0].size();
//最后的分钟数
int res = 0;
//统计新鲜的橘子个数
int good = 0;
//遍历二维数组,将腐烂橘子加入队列,并且统计新鲜橘子个数
for(int i = 0;i < m; ++i)
{
for(int j = 0; j < n; ++j)
{
if(grid[i][j] == 2)
{
q.push( {i,j} );
}
else if(grid[i][j] == 1)
{
good++;
}
}
}
//若没有新鲜橘子,则返回0分钟
if(good == 0)
{
return 0;
}
//当队列不为空时
while( !q.empty())
{
//取出队头结点的坐标并且让队头出队
int x = q.front().first,y = q.front().second;
q.pop();
//分别寻找旧结点上下左右四个方向上的结点
for(int k = 0; k < 4; ++k)
{
int mx = x + dirs[k][0];
int my = y + dirs[k][1];
//若坐标合理且新结点为新鲜橘子则开始进行腐烂操作
if(mx >= 0 && mx < m && my >= 0 && my < n && grid[mx][my] == 1)
{
//将新结点入队
q.push( {mx,my} );
//腐烂到新结点所需的分钟数是旧结点的分钟数+1,因为新旧两结点是相邻的
grid[mx][my] = grid[x][y] + 1;
//返回的结点为最大分钟数
res = max (res,grid[mx][my]);
//新鲜的橘子数量减1
good--;
}
}
}
//若仍有新鲜橘子,说明没有完全腐烂,即返回-1
if(good > 0)
{
return-1;
}
//若全部腐烂,即返回最大分钟数。因为一开始就腐烂的橘子的值为2,所以要减去一开始的数
else
{
return res -2;
}
}
};
上面分析了四道BFS的例题,分别建立在二维数组上的广度搜索,四道题目非常经典,并且有异曲同工之妙,相信大家仔细阅读,能够总结出自己的写法和模板。
如果你觉得对你有帮助,请给写者一个赞和关注,是对我最大的支持,谢谢!