广度优先搜索及其应用

Posted 算法指北

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了广度优先搜索及其应用相关的知识,希望对你有一定的参考价值。

 预计阅读15min



天地不同方觉远,共天无别始知宽。


 ------------唐·曹松《南海》


广度优先搜索(Breadth-First Search,BFS)是最简单的图搜索算法之一,也是许多重要图算法的原型。prim的最小生成树算法和Dijikstra的单元最短路径算法都使用了类似广度优先搜索的思想。
 
本文的目的是阐述BFS 的基本思想,并结合例题进行应用。



算法原理
 
给定图G(V,E) ,V 是节点集合, E是边集合,s为给定的源点。广度优先搜索对图G中的边 进行系统性的搜索来发现可以从源点s到达的所有结点(一个连通枝中的所有节点)
 
搜索的过程可以理解为按一定的层次来遍历与s之间存在通路的点。即最先访问的若干个点一定是直接与s相连的点,这些点访问完之后,再访问与刚才访问的节点相邻但还未访问的节点,也就是与s距离为2的节点,这样进行下去,直到连通枝中所有的节点都被访问。

 

算法实现

以上的描述基本就是BFS的算法思想。我们现在看看如何实现。由于我们是要按层次遍历,必须要记住上一层有哪些节点,并且依次访问与这些节点相邻且还未访问的节点。我们要用到的数据结构是 队列,队列具有先进先出的特点,我们每访问一个节点,就把与之相邻的节点放进队列的末尾。

每次从队列中弹出队头的节点,并判断是否访问过,若为否,则标记为已访问,并把与之相邻的节点放入队列中;若为已访问,则丢弃。弹出下一节点。队列为空时结束。这时整个连通枝均已访问。
 
判断是否访问过也可以放在入队前,已经访问过的节点没必要再入队,因为先前的路径肯定是短一些的。
 


广度优先搜索的算法框架:

框架1(完全版)
  
    
    
  
/*假定图G(V,E)是以邻接表储存的。 我们给每个节点赋予一些额外的属性: u.visited 标记节点u是否访问过。 u.parent 储存u的前驱节点(可用来回溯路径), 没有前驱时parent设为NULL。 u.d用来储存从源节点到u之间的距离。*/
BFS(G,s):    1.初始化变量,对G.V-{s}中的所有节点u: u.visited=false u.parent=NULL u.d=0    2.声明空队列Q    3.将s 入队 Enqueue(Q,s) 4.当Q不空时循环下述过程:             u=Dequeue(Q)//删除队头元素并返回其值给u u.visited=true 对u 的每一个邻点v: 如果v.visited=false: v.d=u.d+1 v.parent=u                              v入队 Enqueue(Q,v)
时间复杂度: 算法只在一个节点出队的时候才对该节点的邻接表进行扫描, 每个邻接链表最多扫描一次,由于所有的长度之和是O( E), 用于扫描的总时间为O( E), 而每个节点入栈一次的总时间为O(V), 初始化操作的成本是O(V),因此总运行时间是O(V+E)



框架2(简便版)
当只想求某两点(s,target)之间的最小间隔时,可以像下面这样
BFS(G,s)    1. 声明visited数组//用来标记哪些访问过,哪些没访问    2. 声明空队列Que    3.visited[s]=true    4.s入队,Enqueue(Que,s)    5.step=0 ,声明n用来储存每一步Que的长度    6.while(Que不空)       6.1.n=Que.length       6.2.for(int i=0;i<n;i++)//保证每次都把本层节点全部弹出             s=Dequeue(Que)      for u in adjacent[s]:                  if(u==target)return step+1//遇到目标节点,返回距离      if visited[u]=false:      visited[u]=true      Enqueue(Que,u)        6.3. step=step+1             



BFS可以用来寻找非加权图(或者所有边权重相同)中任两点的最短路径。
加权图的最短路径可以用dijkstra算法

例:最短的桥



在给定的二维二进制数组 A 中, 存在两座岛。(岛是由四面相连的 1 形成的一个最大组。) 

现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。返回必须翻转的 0 的最小数目。(可以保证答案至少是 1。) 

示例 :
输入:[ [1,1,1,1,1],
             [1,0,0,0,1],
             [1,0,1,0,1],
             [1,0,0,0,1],
             [1,1,1,1,1]] 
输出:1







来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/shortest-bridge 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。








分析:

 由于只存在两座岛,我们可以先找出其中一座,在到达第一座岛的某个节点之后,我们可以用BFS或者DFS找出连通枝上的所有节点,即第一座岛的所有领土。

我们并不知道第一座岛和第二座岛哪两个点之间的距离是最近的,因此我们的策略是将岛屿整体向外一步步扩散,直到我们遇到了另一座岛屿,返回此时的步数即可。

或许下面这种说法会更好理解:

 我们设置一个 ”超级源点”,它与第一座岛中每一个节点都直接相连,那么问题就变成了求超级源点到第二座岛任意一个节点的最短距离。很明显这就变成了一个单源最短路径问题。

如下图 0 是超级源点,1-12 是岛一,13-17是岛二。是不是就很好理解了?


  
    
    
  
class Solution { public: //代码块1 int shortestBridge(vector<vector<int>>& A) { int m = A.size(), n = A[0].size();         queue<pair<int, int>> points;//用于存储遍历过的节点         // dfs寻找第一个岛屿,并把1全部赋值为2         bool flag = false; //标记是否进行过dfs遍历操作 for (int i = 0; i < m; ++i) {             if (flag) break; for (int j = 0; j < n; ++j) {                 if (A[i][j] == 1) {               //从第一个‘1’开始找到第一座岛屿的全部               //并储存在points中 dfs(points, A, i, j);                     flag = true; break; } } }
        //从第一个岛屿bfs //代码块2         int step = 0;//当前距离         int dirR[4] = {-1, 0, 1, 0};//坐标跟新方向 int dirC[4] = {0, 1, 0, -1};         while (!points.empty())         { int k = points.size(); for (int i = 0; i< k; i++) { auto f = points.front(); points.pop(); for (int d =0; d< 4; d++) { int r = dirR[d] + f.first; int c = dirC[d] + f.second; if (r < 0 || r >= A.size() || c < 0 || c >= A[0].size()) continue; if (A[r][c] == 2) continue; if (A[r][c] == 1) return step; A[r][c] = 2; points.push({r, c}); } } step++; } return step;


} //代码块3 void dfs(queue<pair<int, int>>& points,vector<vector<int>>& grid,int rows,int cols){ int nr = grid.size(); int nc = grid[0].size(); grid[rows][cols] = 2; points.push({rows,cols}); if(rows-1>=0&&grid[rows-1][cols]==1){ dfs(points,grid,rows-1,cols); } if(rows+1<nr&&grid[rows+1][cols]==1){ dfs(points,grid,rows+1,cols); } if(cols-1>=0&&grid[rows][cols-1]==1){ dfs(points,grid,rows,cols-1); } if(cols+1<nc&&grid[rows][cols+1]==1){ dfs(points,grid,rows,cols+1); }
}
};




以上是关于广度优先搜索及其应用的主要内容,如果未能解决你的问题,请参考以下文章

基础扩展 | 16. 队列应用示例:广度优先搜索

无向图广度优先遍历及其matlab实现

图相关算法

无向图广度优先遍历及其JAVA实现

广度优先搜索和深度优先搜索

七十九深度和广度优先搜索算法