【算法笔记】8.2 广度优先搜索BFS

  • 这是《算法笔记》的读书记录
  • 本文参考自8.2节

一、引子

  • 还用上一篇DFS中的迷宫例子引入,参考【算法笔记】8.1 深度优先搜索DFS

  • 这次我们改变寻路策略,不知要找出路线,而且想知道从起点到终点的最短步数是多少(两个相邻节点间看作相隔一步),我们可以按如下的图示进行宽度优先搜索
    在这里插入图片描述

    1. 起点A是第一层,发现从A出发能访问到B和C,于是B、C是第二层
    2. 按顺序访问第二层,先看B。从B出发能访问到D和E,于是D、E是第三层,等第二层访问完要按顺序访问
    3. 继续访问第二层的C,从C出发能访问到F和G,于是F、G是第三层,等第二层访问完要按顺序访问,而且它们排在D、E之后
    4. 第二层访问完毕,开始访问第三层的第一个结点D,第四层加入H、I、J
    5. 继续访问第三层的E,第四层加入K、L、M
    6. 继续访问第三层的F,发现F是死胡同,不管他
    7. 访问第三层最后一个点G,发现G是出口,算法结束,后面第四层的结点可以不管了
  • 按照上述分析过程,可以看出层数就是从A出发到达相应点的步数,所有从A到G至少要3步

二、广度优先搜索

1. 定义

  • 广度优先搜索算法(Breadth-First Search,BFS)是一种盲目搜寻法,它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止
  • 广度优先搜索让你能够找出两样东西之间的最短距离,不过最短距离的含义有很多!使用广度优先搜索可以:
    • 编写国际跳棋AI,计算最少走多少步就可获胜;
    • 编写拼写检查器,计算最少编辑多少个地方就可将错拼的单词改成正确的单词,如将READED改为READER需要编辑一个地方;
    • 根据你的人际关系网络找到关系最近的医生

注:上面一段出自《算法图解》
关键点有两个:

  1. BFS适用于找最短距离
  2. 最短距离可以被包装成很多样子出现在不同的题目中

2. 用队列实现BFS

  • 回到引子给出的地图问题,可以看出:BFS的过程很适合用队列实现

    1. A入队
    2. A出队,把A能访问的B、C入队
    3. B出队,把B能访问的D、E入队
  • 因此BFS算法常使用队列实现,且总是按照层次的顺序进行遍历

  • BFS常用模板如下

    void BFS(int s)
    {
          
          
          queue<int> q;
          q.push(s)
          while(!q.empty())
          {
          
          
    			取出队首元素top
    			(访问队首元素top)
    			队首元素出队
    			将top的下一层节点中未曾入队的元素全部入队,并设置为已入队
          }
    }
    
    • BFS的辅助队列常用STL的queue 实现
    • 为了防止有节点重复入队,一般要给每个节点做一个是否入队过的标记。注意是是否入过队而不是是否访问过!
    • 当对队列中元素不只是访问,而且要做修改时,STL的queue不能直接修改,所以最好开个大数组,在队列中存数组下标

三、相关例题

1. 块的个数

  • 给出一个M x N的矩阵,其中元素为0或1,每个元素和它上下左右四个元素是相邻的。如果矩阵中有若干各1是相邻的(不必两两相邻),就称这些1构成了一个 “块”。求给定矩阵中 “块” 的个数

  • 思路:遍历矩阵中每一个元素,如果为0就跳过,如果为1,就用BFS查询和它相邻的四个位置看有没有为1的元素(第一层),如果有就再检查这个块相邻的四个元素(第二层),直到整个块访问完毕。为了防止重复检查,可以设一个数组记录每个位置是否已经被检查过

  • 示例代码

    /*	块的个数 —— BFS	*/
    
    #include<iostream>
    #include<stdio.h>
    #include<queue>
    using namespace std;
    
    const int maxN = 100;
    typedef struct NODE
    {
          
          
    	int x;
    	int y;
    }NODE;
    
    int n, m;							// 矩阵大小
    int matrix[maxN][maxN];				// 01矩阵
    bool inq[maxN][maxN] = {
          
           false };	// 记录元素是否已经入过队
    int X[4] = {
          
           0,0,1,-1 };			// 增量数组
    int Y[4] = {
          
           1,-1,0,0 };
    
    //判断(x,y)是否需要访问 
    bool judge(int x, int y)
    {
          
          
    	if (x >= n || x < 0 || y >= n || y < 0)		//越界了 
    		return false;
    	if (matrix[x][y] == 0 || inq[x][y] == true)	//当前位置是空的或者已经入过队了 
    		return false;
    	return true;
    }
    
    //BFS访问(x,y)元素所在的块,给此块中所有1做标记
    void BFS(int x, int y)
    {
          
          
    	queue<NODE> q;	//辅助队列
    
    	NODE thisNode;	//根结点
    	thisNode.x = x;
    	thisNode.y = y;
    	q.push(thisNode);
    
    	inq[x][y] = 1;
    	while (!q.empty())
    	{
          
          
    		//队首出队
    		NODE top = q.front();
    		q.pop();
    
    		//检查队首的相邻点,为1就入队并打标记
    		for (int i = 0; i < 4; i++)
    		{
          
          
    			int newX = top.x + X[i];
    			int newY = top.y + Y[i];
    
    			if (judge(newX, newY))
    			{
          
          
    				thisNode.x = newX;
    				thisNode.y = newY;
    				q.push(thisNode);
    				inq[newX][newY] = 1;
    			}
    		}
    	}
    }
    
    int main()
    {
          
          
    	//输入矩阵
    	scanf("%d%d", &n, &m);
    	for (int x = 0; x < n; x++)
    		for (int y = 0; y < m; y++)
    			scanf("%d", &matrix[x][y]);
    
    
    	//遍历矩阵,一但有1出现,块计数+1,同时用DFS把块中点都标记上
    	int ans = 0;
    	for (int x = 0; x < n; x++)
    	{
          
          
    		for (int y = 0; y < m; y++)
    		{
          
          
    			if (matrix[x][y] == 1 && inq[x][y] == false)
    			{
          
          
    				ans++;
    				BFS(x, y);	//把此块的所有1的inq标记为true 
    			}
    		}
    	}
    	printf("%d\n", ans);
    	system("pause");
    	return 0;
    }
    
    /* 样例输入
    6 7
    0 1 1 1 0 0 1
    0 0 1 0 0 0 0
    0 0 0 0 1 0 0
    0 0 0 1 1 1 0
    1 1 1 1 1 0 0
    1 1 1 1 0 0 0
    */
    
  • 这个题也可以用DFS递归实现,直接套DFS模板,修改上面的BFS函数如下即可,其他都不用变

    void DFS(int x,int y)
    {
          
          
    	//边界判断
    	if (!judge(x, y))
    		return;
    
    	//当前点是块中点,先标记,然后横向遍历解答树所有子节点进行DFS  
    	inq[x][y] = 1;
    	for (int i = 0; i < 4; i++)
    	{
          
          
    		int newX = x + X[i];
    		int newY = y + Y[i];
    
    		if (judge(newX, newY))
    			DFS(newX,newY);
    	}
    	
    }
    

猜你喜欢

转载自blog.csdn.net/wxc971231/article/details/108498468