图的广度优先搜索(bfs)以及深度优先搜索(dfs)
1.前言
和树的遍历类似,图的遍历也是从图中某点出发,然后按照某种方法对图中所有顶点进行访问,且仅访问一次。
但是图的遍历相对树而言要更为复杂。因为图中的任意顶点都可能与其他顶点相邻,所以在图的遍历中必须记录已被访问的顶点,避免重复访问。
根据搜索路径的不同,我们可以将遍历图的方法分为两种:广度优先搜索和深度优先搜索。
2.图的基本概念
无向图:顶点对(u,v)是无序的,即(u,v)和(v,u)是同一条边。常用一对圆括号表示。如图所示:
有向图:顶点对<u,v>是有序的,它是指从顶点u到顶点 v的一条有向边。其中u是有向边的始点,v是有向边的终点。常用一对<>表示。如图所示:
连通图:在无向图G中,从顶点v到顶点v’有路径,则称v和v’是联通的。若图中任意两顶点v、v’∈V,v和v’之间均联通,则称G是连通图。上述两图均为连通图。如图所示:
非连通图:若无向图G中,存在v和v’之间不连通,则称G是非连通图。如图所示:
3.广度优先搜索
基本思想:宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
广度优先搜索算法的搜索步骤一般是:
(1)从队列头取出一个结点,检查它按照扩展规则是否能够扩展,如果能则产生一个新结点。
(2)检查新生成的结点,看它是否已在队列中存在,如果新结点已经在队列中出现过,就放弃这个结点,然后回到第(1)步。否则,如果新结点未曾在队列中出现过,则将它加入到队列尾。
(3)检查新结点是否目标结点。如果新结点是目标结点,则搜索成功,程序结束;若新结点不是目标结点,则回到第(1)步,再从队列头取出结点进行扩展。
最终可能产生两种结果:找到目标结点,或扩展完所有结点而没有找到目标结点。
如果目标结点存在于解答树的有限层上,广度优先搜索算法一定能保证找到一条通向它的最佳路径,因此广度优先搜索算法特别适用于只需求出最优解的问题。当问题需要给出解的路径,则要保存每个结点的来源,也就是它是从哪一个节点扩展来的。
对于广度优先搜索算法来说,问题不同则状态结点的结构和结点扩展规则是不同的,但搜索的策略是相同的。
广度优先搜索如图所示:
1.初始时全部顶点均未被访问,visited数组初始化为0,队列中没有元素。
2.即将访问顶点v0。
3.访问顶点v0,并置visited[0]的值为1,同时将v0入队。
4.将v0出队,访问v0的邻接点v2。判断visited[2],因为visited[2]的值为0,访问v2。
5.将visited[2]置为1,并将v2入队。
6.访问v0邻接点v1。判断visited[1],因为visited[1]的值为0,访问v1。
7.将visited[1]置为0,并将v1入队。
8.判断visited[3],因为它的值为0,访问v3。将visited[3]置为0,并将v3入队。
9.v0的全部邻接点均已被访问完毕。将队头元素v2出队,开始访问v2的所有邻接点。
开始访问v2邻接点v0,判断visited[0],因为其值为1,不进行访问。
继续访问v2邻接点v4,判断visited[4],因为其值为0,访问v4,如下图:
10.将visited[4]置为1,并将v4入队。
11.v2的全部邻接点均已被访问完毕。将队头元素v1出队,开始访问v1的所有邻接点。
开始访问v1邻接点v0,因为visited[0]值为1,不进行访问。
继续访问v1邻接点v4,因为visited[4]的值为1,不进行访问。
继续访问v1邻接点v5,因为visited[5]值为0,访问v5,如下图:
12.将visited[5]置为1,并将v5入队。
13.v1的全部邻接点均已被访问完毕,将队头元素v3出队,开始访问v3的所有邻接点。
开始访问v3邻接点v0,因为visited[0]值为1,不进行访问。
继续访问v3邻接点v5,因为visited[5]值为1,不进行访问。
14.v3的全部邻接点均已被访问完毕,将队头元素v4出队,开始访问v4的所有邻接点。
开始访问v4的邻接点v2,因为visited[2]的值为1,不进行访问。
继续访问v4的邻接点v6,因为visited[6]的值为0,访问v6,如下图:
15.将visited[6]值为1,并将v6入队。
16.v4的全部邻接点均已被访问完毕,将队头元素v5出队,开始访问v5的所有邻接点。
开始访问v5邻接点v3,因为visited[3]的值为1,不进行访问。
继续访问v5邻接点v6,因为visited[6]的值为1,不进行访问
17.v5的全部邻接点均已被访问完毕,将队头元素v6出队,开始访问v6的所有邻接点。
开始访问v6邻接点v4,因为visited[4]的值为1,不进行访问。
继续访问v6邻接点v5,因为visited[5]的值文1,不进行访问。
18.队列为空,退出循环,全部顶点均访问完毕。
广度优先搜索例题:
【例一】 有一个宽为W、高为H的矩形平面,用黑色和红色两种颜色的方砖铺满。一个小朋友站在一块黑色方块上开始移动,规定移动方向有上、下、左、右四种,且只能在黑色方块上移动(即不能移到红色方块上)。编写一个程序,计算小朋友从起点出发可到达的所有黑色方砖的块数(包括起点)。
例如,如图1所示的矩形平面中,“#”表示红色砖块,“.”表示黑色砖块,“@”表示小朋友的起点,则小朋友能走到的黑色方砖有28块。
代码如下:
#include <iostream>
using namespace std;#define N 21
struct Node
{int x;int y;
};int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
char map[N][N];
int visit[N][N];
int bfs(int startx, int starty,int w,int h)
{Node q[N*N],cur,next; // q为队列int front,rear; // front为队头指针,rear为队尾指针int i,x,y,sum; front=rear=0; // 队列q初始化sum=0;cur.x=startx; cur.y=starty; visit[startx][starty]=1;q[rear++]=cur; // 初始结点入队while(rear!=front) // 队列不为空{cur=q[front++]; // 队头元素出队sum++; // 方砖计数for (i=0;i<4;i++){x=cur.x+dx[i]; y=cur.y+dy[i];if(x >=0 && x<h && y>=0 && y<w && map[x][y]!='#' && visit[x][y]==0){visit[x][y] = 1;next.x=x; next.y=y; // 由cur扩展出新结点nextq[rear++]=next; // next结点入队} }}return sum;
}int main()
{int i,j,pos_x,pos_y,w,h,sum;while(1){cin>>w>>h; if(w==0 && h==0) break;for(i=0;i<h;i++){for(j=0;j<w;j++){cin>>map[i][j];if(map[i][j]=='@'){pos_x = i;pos_y = j;}visit[i][j] = 0;}}sum=bfs(pos_x, pos_y,w,h);cout<<sum<<endl;}return 0;
}
4.深度优先搜索
基本思想:深度优先搜索是一种在开发爬虫早期使用较多的方法。它的目的是要达到被搜索结构的叶结点(即那些不包含任何超链的HTML文件) 。在一个HTML文件中,当一个超链被选择后,被链接的HTML文件将执行深度优先搜索,即在搜索其余的超链结果之前必须先完整地搜索单独的一条链。深度优先搜索沿着HTML文件上的超链走到不能再深入为止,然后返回到某一个HTML文件,再继续选择该HTML文件中的其他超链。当不再有其他超链可选择时,说明搜索已经结束。
深度优先搜索DFS(Depth First Search)是从初始结点开始扩展,扩展顺序总是先扩展最新产生的结点。这就使得搜索沿着状态空间某条单一的路径进行下去,直到最后的结点不能产生新结点或者找到目标结点为止。当搜索到不能产生新的结点的时候,就沿着结点产生顺序的反方向寻找可以产生新结点的结点,并扩展它,形成另一条搜索路径。
为了便于进行搜索,要设置一个表存储所有的结点。由于在深度优先搜索算法中,要满足先生成的结点后扩展的原则,所以存储结点的表一般采用栈这种数据结构。
深度优先搜索算法的搜索步骤一般是:
(1)从初始结点开始,将待扩展结点依次放到栈中。
(2)如果栈空,即所有待扩展结点已全部扩展完毕,则问题无解,退出。
(3)取栈中最新加入的结点,即栈顶结点出栈,并用相应的扩展原则扩展出所有的子结点,并按顺序将这些结点放入栈中。若没有子结点产生,则转(2)。
(4)如果某个子结点为目标结点,则找到问题的解(这不一定是最优解),结束。如果要求得问题的最优解,或者所有解,则转(2),继续搜索新的目标结点。
深度优先搜索如图所示:
1.初始时所有顶点均未被访问,visited数组为空。
2.即将访问v0。
3.访问v0,并将visited[0]的值置为1。
4.访问v0的邻接点v2,判断visited[2],因其值为0,访问v2。
5.将visited[2]置为1。
6.访问v2的邻接点v0,判断visited[0],其值为1,不访问。
继续访问v2的邻接点v4,判断visited[4],其值为0,访问v4。
7.将visited[4]置为1。
8.访问v4的邻接点v1,判断visited[1],其值为0,访问v1。
9.将visited[1]置为1。
10.访问v1的邻接点v0,判断visited[0],其值为1,不访问。
继续访问v1的邻接点v4,判断visited[4],其值为1,不访问。
继续访问v1的邻接点v5,判读visited[5],其值为0,访问v5。
11.将visited[5]置为1。
12.访问v5的邻接点v1,判断visited[1],其值为1,不访问。
继续访问v5的邻接点v3,判断visited[3],其值为0,访问v3。
13.将visited[1]置为1。
14.访问v3的邻接点v0,判断visited[0],其值为1,不访问。
继续访问v3的邻接点v5,判断visited[5],其值为1,不访问。
v3所有邻接点均已被访问,回溯到其上一个顶点v5,遍历v5所有邻接点。
访问v5的邻接点v6,判断visited[6],其值为0,访问v6。
15.将visited[6]置为1。
16.访问v6的邻接点v4,判断visited[4],其值为1,不访问。
访问v6的邻接点v5,判断visited[5],其值为1,不访问。
v6所有邻接点均已被访问,回溯到其上一个顶点v5,遍历v5剩余邻接点。
17.v5所有邻接点均已被访问,回溯到其上一个顶点v1。
v1所有邻接点均已被访问,回溯到其上一个顶点v4,遍历v4剩余邻接点v6。
v4所有邻接点均已被访问,回溯到其上一个顶点v2。
v2所有邻接点均已被访问,回溯到其上一个顶点v1,遍历v1剩余邻接点v3。
v1所有邻接点均已被访问,搜索结束。
深度优先搜索例题:
【例1】黑色方块
有一个宽为W、高为H的矩形平面,用黑色和红色两种颜色的方砖铺满。一个小朋友站在一块黑色方块上开始移动,规定移动方向有上、下、左、右四种,且只能在黑色方块上移动(即不能移到红色方块上)。编写一个程序,计算小朋友从起点出发可到达的所有黑色方砖的块数(包括起点)。
例如,如图1所示的矩形平面中,“#”表示红色砖块,“.”表示黑色砖块,“@”表示小朋友的起点,则小朋友能走到的黑色方砖有28块。
代码如下:
#include <iostream>
using namespace std;#define N 21
struct Node
{int x;int y;
};
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
char map[N][N];
int visit[N][N];
int dfs(int startx, int starty,int w,int h)
{Node s[N*N],cur,next; // s为栈int top,i,x,y,sum; // top为栈顶指针top=-1; // 栈S初始化sum=0;cur.x=startx; cur.y=starty; visit[startx][starty]=1;s[++top]=cur; // 初始结点入栈;while(top>=0) // 栈不为空{cur=s[top--]; // 栈顶元素出栈sum++; // 方砖计数for (i=0;i<4;i++){x=cur.x+dx[i]; y=cur.y+dy[i];if(x >=0 && x<h && y>=0 && y<w && map[x][y]!='#' && visit[x][y]==0){visit[x][y] = 1;next.x=x; next.y=y; // 由cur扩展出新结点nexts[++top]=next; // next结点入栈}}}return sum;
}int main()
{int i,j,pos_x,pos_y,w,h,sum;while(1){cin>>w>>h;if (w==0 && h==0) break;for(i=0;i<h;i++){for(j=0;j<w;j++){cin>>map[i][j];if (map[i][j]=='@'){pos_x = i; pos_y = j;}visit[i][j] = 0;}}sum=dfs(pos_x, pos_y,w,h);cout<<sum<<endl;}return 0;
}
对于同样的问题,如果深搜和广搜都可以的话,推荐使用广搜,因为广搜不用递归调用系统栈,所以它的速度更快一些。
参考博客:
https://blog.csdn.net/weixin_40953222/article/details/80544928
https://www.cnblogs.com/cs-whut/p/11147213.html
努力加油a啊,(o)/~
图的广度优先搜索(bfs)以及深度优先搜索(dfs)相关推荐
- (造轮子)C 创建队列和图实现广度优先算法(BFS)和深度优先算法(DFS)(数据结构)
链表.队列和图实现BFS和DFS算法(C+造轮子+详细代码注释) 1.队列的链式存储结构 队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表.头指针指向队头节点,尾指针指向 ...
- matlab bfs函数,matlab练习程序(广度优先搜索BFS、深度优先搜索DFS)
如此经典的算法竟一直没有单独的实现过,真是遗憾啊. 广度优先搜索在过去实现的二值图像连通区域标记和prim最小生成树算法时已经无意识的用到了,深度优先搜索倒是没用过. 这次单独的将两个算法实现出来,因 ...
- 图论算法(5):图的广度优先遍历 BFS
本章节内容使用 java 实现,Github 代码仓:https://github.com/ZhekaiLi/Code/tree/main/Graph/src 查看文章内的图片可能需要科学上网! 因为 ...
- 【vivo2021届秋季校招编程题】【java】广度优先搜索(BFS)/深度优先搜索(DFS)找最短路径长度
vivo2021届秋季校招编程题 图中 找两点间的最短路径长度 广度搜索bfs/深度搜索dfs vivo游戏中心的运营小伙伴最近接到一款新游戏的上架申请,为了保障用户体验,运营同学将按运营流程和规范对 ...
- 广度优先搜索(BSF)和深度优先搜索(DSF)示例
输入数据 示例代码 #include <iostream> #include <queue> #include <stack> #include <vecto ...
- java数据结构和算法——图的广度优先(BFS)遍历
目录 一.图的遍历介绍 二.图的广度优先搜索(Broad First Search) 三.图的广度优先遍历算法步骤 四.图的广度优先遍历示例需求 五.图的广度优先遍历代码示例 一.图的遍历介绍 所谓图 ...
- 算法训练营 搜索技术(深度优先搜索)
回溯法 回溯法指从初始状态出发,按照深度优先搜索的方式,根据产生子节点的条件约束,搜索问题的解,当发现当前节点不满足求解条件时,就回溯,尝试其他路径. 回溯法是一种"能进则进,进不了就换,换 ...
- Java数据结构之图的基本概念和算法,深度优先遍历DFS,广度优先遍历BFS(图解)
文章目录 前言 一.图的基本概念 1.图的定义 2.基本术语 二.图的基本算法 1.初始化图 2.插入顶点和边 3.矩阵打印 4.返回第一个邻接结点的下标 5.返回第一个邻接结点的下一个结点的下标 三 ...
- 搜索专题之深度优先搜索(DFS)
什么是DFS? 深度优先遍历图的方法是,从图中某顶点v出发: (1)访问顶点v: (2)依次从v的未被访问的邻接点出发,对图进行深度优先遍历:直至图中和v有路径相通的顶点都被访问: (3)若此时图中尚 ...
最新文章
- php如何包含html模板,php html模板中怎么使用类似include的功能?
- Linux+Apache+MySQL+PHP5的安装与配置与phpBB2论坛的架设
- HDU 6143 Killer Names (组合数学+DP)
- Apache模块开发
- Golang的导包和引用包的问题
- ASP.NET Core分布式项目实战(集成ASP.NETCore Identity)--学习笔记
- java务必让常量的值在运行期保持不变
- Android 自定义Switch,仿微信开关键Switch
- 2021年Github项目Top100
- linq学习笔记(1):c#3.0新特性(2)
- PLSQL连接远程Oracle出现ORA-12541: 无监听程序
- Go语言潜力有目共睹,但它的Goroutine机制底层原理你了解吗?
- 【一起学爬虫】爬虫实战:爬取京东零食
- 2022 Java面试题
- 理工专业单身男终极把妹大法
- bmp格式的图片怎么转jpg格式?怎么快速转图片格式?
- 华为主题引擎怎么下载_华为搜索引擎app
- 用ECMAScript4 ( ActionScript3) 实现Unity的热更新 -- Demo分析
- 李宏毅nlp学习笔记10:QA(Question answering)
- Linux文件查看和编辑命令
热门文章
- socket与文件描述符
- uni-app 发送form-data参数的请求方式传值给后台
- Maven 在 mac os M1芯片 上的安装
- IOS之UIToolBar约束报错
- icp光谱仪的工作原理_ICP2060T ICP光谱仪
- linux 目录大小是12288,为什么有些目录数的引用超过3,为什么很多目录的大小都是4096...
- element中el-upload和vue-cropper结合实现上传头像裁剪大小
- 深入理解多重采样(Multisampling)
- PHP中 $_SERVER的信息汇总
- iOS中常用的四种数据持久化方法