可能是目前为止最为详细的深度优先搜索DFS和广度优先搜索BFS算法分析

Fredrica ·
更新时间:2024-09-20
· 967 次阅读

图的遍历是指从图中某一顶点出发,按照某种搜索方法沿着图中的边对图中的所有顶点访问一次,且仅访问一次。图的遍历常见算法有BFS和DFS。

文章目录(一)深度优先搜索DFS1、基本思路2、图示3、算法性能分析4、深度优先遍历的非递归写法(二)广度优先遍历BFS1、基本思想2、图示3、算法性能分析4、应用---BFS算法求解非带权图单源最短路径问题(三)经典算法题目分析1.Red and Black2、最优配餐问题3、CCF201604-4 游戏(四)参考文献 (一)深度优先搜索DFS 1、基本思路

    DFS 用于找所有解的问题,它的空间效率高,但是找到的不一定是最优解,必须记录并完成整个搜索,故一般情况下,深搜需要非常高效的剪枝。DFS类似于树的先序遍历,搜索策略为尽可能“深”的搜索一个图。首先访问图中某一起始顶点v,然后由v出发,访问与v邻接且未被访问的任一顶点w1,再访问与w1邻接且未被访问的任意顶点w2,…重复上述过程。当不能再继续向下访问时,依次退回到最近被访问的顶点,若它还有邻接顶点未被访问过,则从该点开始继续上述搜索过程,知道图中所有顶点均被访问过为止。程序伪代码如下:

bool visited[MAX_VERTEX_NUM];//访问标记数组 void DFSTraverse(Graph G){ //对图G进行深度优先遍历,访问函数为visit() for(v=0;v=0;w=NextNeighbor(G,v,w)) if(!visited[w]){//w为u的尚未访问的邻接顶点 DFS(G,w); } } 2、图示

DFS

3、算法性能分析

   DFS算法是一个递归算法,需要借助一个递归工作栈,故它的空间复杂度为O(|V|)。遍历图的过程实质上是对每个顶点查找其邻接点的过程,其耗费的时间取决于所采用的存储结构。当以邻接矩阵表示时,查找每个顶点的邻接点所需时间为O(|V|),故总的时间复杂度为O(|V*V|)。当以邻接表表示时,查找所有顶点的邻接点所需时间为O(|E|),访问顶点所需时间为O(|V|),此时,总的时间复杂度为O(|V|+|E|)。

4、深度优先遍历的非递归写法

在深度优先搜索的非递归算法中使用一个栈S,用来记忆下一步可能访问的顶点,同时使用了一个访问标记数组visited[i],在visited[i]中记忆第i个顶点是否在站内或者曾经在栈内。若是,以后他不能再进栈。图采用邻接表方式,伪代码如下所示:

void DFS_Non_Rc(AGraph& G,int v){ //从顶点v开始进行深度优先搜索,一次遍历一个连通分量的所有顶点 int w;//顶点序号 InitStack(S);//初始化栈S for(i=0;i=0;w=NextNeighbor(G,k,w)) if(!visited[w]){//未进过栈的顶点进栈 Push(S,w); visited[w]=true;//作标记,以免再次入栈 }//end if }//end while } (二)广度优先遍历BFS 1、基本思想

    BFS 常用于找单一的最短路线,它的特点是 “搜到就是最优解”,类似于二叉树的层序遍历算法,它的基本思想是:首先访问起始顶点v,接着由v出发,依次访问v的各个未访问过的邻接顶点w1,w2,w3,…wi,然后再依次访问w1,w2,…,wi的所有未被访问过的邻接顶点…依次类推,直到图中所有顶点都被访问过为止。类似的思想还将应用于Dijkstra单源最短路径算法和Prim最小生成树算法。其实现借助于一个辅助队列。伪代码如下所示:

bool visited[MAX_BERTEX_NUM];//访问标记数组 void BFSTraverse(Graph G){ //对图G进行广度优先遍历,设访问函数为visit() for(i=0;i<G.vexnum,++i) visited[i]=FALSE; InitQueue(Q); for(i=0;i=0;w=NextNeighbor(G,v,w)) //检测v所有邻接点 if(!visited[w]){//w为v的尚未访问的邻接顶点 visit(w);//访问顶点w visited[w]=true;//对w做已访问标记 EnQueue(Q,w);//顶点w入队列 } } } 2、图示

BFS

3、算法性能分析

   无论是使用邻接表还是邻接矩阵的存储方式,BFS算法都需要借助一个辅助队列Q,n个顶点均需入队一次,在最坏的情况下,空间复杂度为O(|V|)。
   当采用邻接表存储方式时,每个顶点均需搜索一次(或入队一次),故时间复杂度为O(|V|),在搜索任一顶点的邻接点时,每条边至少访问一次,故时间复杂度为O(|E|),算法总时间复杂度为O(|V|+|E|)。当采用邻接矩阵存储方式时,查找每个顶点的邻接表所需时间为O(|V|),故算法总时间复杂度为O(|V|^2)。

4、应用—BFS算法求解非带权图单源最短路径问题 void BFS_MIN_Distance(Graph G,int u){ //d[i]表示从u到i结点的最短路径 for(i=0;i=0;w=NextNeighbor(G,u,w)) if(!visited[w]){ //w为u尚未访问的邻接顶点 visited[w]=true; d[w]=d[u]+1;//路径长度+1 EnQueue(Q,w);//顶点w入队 }//if }//while } (三)经典算法题目分析 1.Red and Black

There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can’t move on red tiles, he can move only on black tiles.
Write a program to count the number of black tiles which he can reach by repeating the moves described above.

Input:

The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20.
There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.
‘.’ - a black tile
‘#’ - a red tile
‘@’ - a man on a black tile(appears exactly once in a data set)
The end of the input is indicated by a line consisting of two zeros.

Output:

For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself).

SampleInput:

6 9
…#.
…#





#@…#
.#…#.
11 9
.#…
.#.#######.
.#.#…#.
.#.#.###.#.
.#.#…@#.#.
.#.#####.#.
.#…#.
.#########.

11 6
…#…#…#…
…#…#…#…
…#…#…###
…#…#…#@.
…#…#…#…
…#…#…#…
7 7
…#.#…
…#.#…
###.###
…@…
###.###
…#.#…
…#.#…
0 0

Sample Output

45
59
6
13

题目大意:题意:给你一张图,图上有黑色,红色两种方块,人只能走黑色块,问你
人最多能走多少个黑色块
输入:
w、h代表图的宽度高度,再给出图
@代表人的位置,#代表红色,.代表黑色

使用两种搜索方式进行搜索,如图所示
该题主要是用bfs/dfs求最优路径,属于暴力搜索,上图是分别使用dfs和bfs算法走的路径示意图,下面是两种方法代码

/** 使用DFS算法进行搜索最优 */ #include #include #include using namespace std; int vis[25][25];//标记是否走过 char map[25][25];//走的图 int to[4][2]={0,1,0,-1,1,0,-1,0};//相邻位置 int ans;//记录结果 int w,h; void dfs(int x,int y){ if(vis[x][y]) return ;//已经访问过,则不访问 if(map[x][y]=='#') return;//若为#则不访问 vis[x][y]=1;//标记已访问 ans++;//步数+1 for(int i=0;i=1&&xx=1&&yy<=w){ dfs(xx,yy); } } } } int main(int argc, char** argv) { int x,y; while(~scanf("%d%d",&w,&h)&&w+h){ ans=0; memset(vis,0,sizeof(vis)); for(int i=1;i<=h;i++){ scanf("%s",map[i]+1); for(int j=1;j<=w;j++){ if(map[i][j]=='@'){ x=i; y=j; } } } dfs(x,y); printf("%d\n",ans); } return 0; } /** 使用BFS算法进行搜索最优 */ #include #include #include #include using namespace std; int vis[25][25]; char map[25][25]; int to[4][2]={0,1,0,-1,1,0,-1,0};//相邻位置 int ans; int w,h; struct node{ int x,y; }; // queue q;//需要用到队列 void bfs(int sx,int sy){ //若队列不为空,则出队 ,为了保证队此时为空 while(!q.empty()){ q.pop(); } node a; a.x=sx; a.y=sy; //入队 q.push(a); //置为已经访问过 vis[sx][sy]=1; ans++;//步数++ while(!q.empty()){ //取队首元素 node cur=q.front(); q.pop();//出队 //在四个方向逐层进行搜索 for(int i=0;i<4;i++){ int xx=cur.x+to[i][0]; int yy=cur.y+to[i][1]; //若超出了范围,则跳过 if(xx<1||yyh||yy>w){ continue; } //若已经访问过 if(vis[xx][yy]){ continue; } //若为'#'代表此路不通 if(map[xx][yy]=='#'){ continue; } vis[xx][yy]=1; ans++; node b; b.x=xx; b.y=yy; q.push(b); } } } int main(){ int x,y; while(~scanf("%d%d",&w,&h)&&w+h){ ans=0; memset(vis,0,sizeof(vis)); for(int i=1;i<=h;i++){ //+1跳过'\0' scanf("%s",map[i]+1); //printf("%s\n",map[i]+1); for(int j=1;j<=w;j++){ //获得初始位置 if(map[i][j]=='@'){ x=i; y=j; } } } bfs(x,y); printf("%d\n",ans); } return 0; } 2、最优配餐问题 题目描述

栋栋最近开了一家餐饮连锁店,提供外卖服务。随着连锁店越来越多,怎么合理的给客户送餐成为了一个急需解决的问题。
    栋栋的连锁店所在的区域可以看成是一个n×n的方格图(如下图所示),方格的格点上的位置上可能包含栋栋的分店(绿色标注)或者客户(蓝色标注),有一些格点是不能经过的(红色标注)。
   方格图中的线表示可以行走的道路,相邻两个格点的距离为1。栋栋要送餐必须走可以行走的道路,而且不能经过红色标注的点。
   送餐的主要成本体现在路上所花的时间,每一份餐每走一个单位的距离需要花费1块钱。每个客户的需求都可以由栋栋的任意分店配送,每个分店没有配送总量的限制。
   现在你得到了栋栋的客户的需求,请问在最优的送餐方式下,送这些餐需要花费多大的成本。

在这里插入图片描述

输入格式

输入的第一行包含四个整数n, m, k, d,分别表示方格图的大小、栋栋的分店数量、客户的数量,以及不能经过的点的数量。
    接下来m行,每行两个整数xi, yi,表示栋栋的一个分店在方格图中的横坐标和纵坐标。
    接下来k行,每行三个整数xi, yi, ci,分别表示每个客户在方格图中的横坐标、纵坐标和订餐的量。(注意,可能有多个客户在方格图中的同一个位置)
    接下来d行,每行两个整数,分别表示每个不能经过的点的横坐标和纵坐标。

输出格式

输出一个整数,表示最优送餐方式下所需要花费的成本。

样例输入

10 2 3 3
1 1
8 8
1 5 1
2 3 3
6 7 2
1 2
2 2
6 8

样例输出

29

该题与上一道题的不同之处在于:上一道为单源求最优,本道题是多源(一至多个分店)求最优,使用BFS实现最佳

#include #include #include using namespace std; const int N=1000; const int TRUE=1; const int DIRECTSIZE=4; //定义方向结构体 struct direct{ int drow,dcol; }direct[DIRECTSIZE]={{-1,0},{1,0},{0,-1},{0,1}} ; int buyer[N+1][N+1];//存储顾客所在位置 int visited[N+1][N+1];//标记是否访问过 struct node{ int row,col,step; node(){ } //自家分店构造函数 node(int r,int c,int s){ row=r; col=c; step=s; } }; queue q; int count=0;//订餐点总数 long long ans=0; //多源点进行遍历 void bfs(int n){ node front,v; while(!q.empty()){ //首先将队首出队,从第一家店开始搜索 front=q.front(); q.pop(); for(int i=0;i<DIRECTSIZE;i++){ //移动一格 v.row=front.row+direct[i].drow; v.col=front.col+direct[i].dcol; //步数加1 v.step=front.step+1; //若行列越界,则跳过 if(v.rown||v.coln) continue; if(visited[v.row][v.col]) continue; //如果是订餐点,则计算成本并且累加 if(buyer[v.row][v.col]>0){ visited[v.row][v.col]=1; //点一个餐送一个人,有可能这些顾客在一个点 ans+=buyer[v.row][v.col]*v.step; //若已经遍历完所有买家,则return if(--count==0){ return ; } } //向前继续搜索 visited[v.row][v.col]=1; q.push(v);//将v加入队尾,表示已经访问过 } } } /** 先将所有的餐厅信息(坐标以及步数)入队, 在遍历一个店铺之后就会将扩展的上右下左四个方向入队, 直到最后一个餐厅结束,就完成了所有店铺的扩展。 以此类推,将每一个点都要遍历一下。每到达客户的地点,就会计算相应的费用。 */ int main(){ int m,k,d,x,y,c; memset(buyer,0,sizeof(buyer)); memset(visited,0,sizeof(visited)); //输入数据 cin>>n>>m>>k>>d; for(int i=1;i>x>>y; //将各个分店加入队列中 q.push(node(x,y,0)) ; visited[x][y]=true; } for(int i=0;i>x>>y; cin>>c; //统计客户所在地点数量(多个客户可能在同一地点) if(buyer[x][y]==0){ count++;//客户所在地点数量 } buyer[x][y]+=c;//统计某个地点的订单数量 } //将不能经过的坐标置为true for(int i=0;i>x>>y; visited[x][y]=true; } //广度优先搜索 bfs(n); cout<<ans<<endl; return 0; } 3、CCF201604-4 游戏 问题描述

 小明在玩一个电脑游戏,游戏在一个n×m的方格图上进行,小明控制的角色开始的时候站在第一行第一列,目标是前往第n行第m列。
 方格图上有一些方格是始终安全的,有一些在一段时间是危险的,如果小明控制的角色到达一个方格的时候方格是危险的,则小明输掉了游戏,如果小明的角色到达了第n行第m列,则小明过关。第一行第一列和第n行第m列永远都是安全的。
 每个单位时间,小明的角色必须向上下左右四个方向相邻的方格中的一个移动一格。
 经过很多次尝试,小明掌握了方格图的安全和危险的规律:每一个方格出现危险的时间一定是连续的。并且,小明还掌握了每个方格在哪段时间是危险的。
 现在,小明想知道,自己最快经过几个时间单位可以达到第n行第m列过关。

输入格式

  输入的第一行包含三个整数n, m, t,用一个空格分隔,表示方格图的行数n、列数m,以及方格图中有危险的方格数量。
  接下来t行,每行4个整数r, c, a, b,表示第r行第c列的方格在第a个时刻到第b个时刻之间是危险的,包括a和b。游戏开始时的时刻为0。输入数据保证r和c不同时为1,而且当r为n时c不为m。一个方格只有一段时间是危险的(或者说不会出现两行拥有相同的r和c)。

输出格式

输出一个整数,表示小明最快经过几个时间单位可以过关。输入数据保证小明一定可以过关。

样例输入

3 3 3
2 1 1 1
1 3 2 10
2 2 2 10

样例输出

6

样例说明

   第2行第1列时刻1是危险的,因此第一步必须走到第1行第2列。
  第二步可以走到第1行第1列,第三步走到第2行第1列,后面经过第3行第1列、第3行第2列到达第3行第3列。

评测用例规模与约定

  前30%的评测用例满足:0 < n, m ≤ 10,0 ≤ t < 99。
  所有评测用例满足:0 < n, m ≤ 100,0 ≤ t < 9999,1 ≤ r ≤ n,1 ≤ c ≤ m,0 ≤ a ≤ b ≤ 100。

问题分析
本题需要一个三维的标志来避免重复搜索。除了行列坐标外,需要考虑时间问题,故将其设置为三维数组。因为一些格在某个时间范围是危险的,不可进入,但是这个时间范围之外,是可以随意进入的。所以有时候需要在一些地方踱步,等过了这段时间再前行,就不能简单地限制为进入过的格不能再进入。 #include #include #include using namespace std; const int N=100; const int DIRECTSIZE=4; struct direct{ int drow,dcol; }direct[DIRECTSIZE]={{-1,0},{1,0},{0,-1},{0,1}}; //需要定义个三维数组,第三维存储在这个时间是否可以通过 int visited[N+1][N+1][300+1]; struct node{ int row,col; int level; }; int bfs(int n,int m){ node start,front,v; //从第一行第一列开始走 start.row=1; start.col=1; start.level=0; queue q; q.push(start); while(!q.empty()){ front=q.front(); q.pop(); //设置出口 //到达终点则结束 if(front.row==n&&front.col==m) return front.level; for(int i=0;i<DIRECTSIZE;i++){ //四个方向各向前走一步 v.row=front.row+direct[i].drow; v.col=front.col+direct[i].dcol; v.level=front.level+1; //行界越界则跳过 if(v.rown||v.colm){ continue; } //已经访问过的点无法再次访问 if(visited[v.row][v.col][v.level]) continue; //向前搜索:标记v点为已经访问过,v点加入队列中 visited[v.row][v.col][v.level]=1; q.push(v); } } return 0; } int main(){ int n,m,t,r,c,a,b; memset(visited,0,sizeof(visited)); cin>>n>>m>>t; for(int i=1;i>r>>c>>a>>b; //设置方格危险时间,使之那些时间不可进入 for(int j=a;j<=b;j++){ visited[r][c][j]=1; } } int ans=bfs(n,m); cout<<ans<<endl; return 0; } (四)参考文献

【1】2018年数据结构考研复习指导. 王道论坛组编.
【2】https://www.cnblogs.com/kungfupanda/p/11248014.html
【3】https://vjudge.net/problem/POJ-1979
【4】参考视频 https://www.bilibili.com/video/av78091226?from=search&seid=16502200292163627678
【5】https://blog.csdn.net/tigerisland45/article/details/54934916


作者:Evan_love



dfs

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章