深度优先搜索(DFS)与宽度优先搜索(BFS)解析及例题_c语言

深度优先搜索(DFS)

1.定义

从某个状态开始,不断地转移状态直到无法转移,然后回退到前一步状态,继续转移到其他状态,如此不断重复,直到找到最终的解。
根据深度优先搜索的特点,采用递归函数实现比较简单。
深度优先搜索(隐式地)利用了栈进行计算。

2.状态转移图

在这里插入图片描述

3.例题
3.1 部分和问题

问题描述
给定正整数a1,a2,…,an,判断是否可以从中选出若干数,使他们的和恰好为k。
限制条件

  • 1<=n<=20
  • -108<=ai<=108
  • -108<=k<=108

样例一
输入:n=4,k=13,a={1,2,4,7}
输出:Yes
解释:13=2+4+7
样例二
输入:n=4,k=15,a={1,2,4,7}
输出:No
分析
从a1开始按顺序决定每个数加或不加,在全部n个数都决定后再判断它们的和是不是k即可。因为状态数是2n,所以复杂度为O(2n)。
在这里插入图片描述
代码

//DFS
#include<stdio.h>
#define MAX_N 20
int a[MAX_N]; //存储要求和的数据 
int n,k;      //n代表数据个数,k代表要求出的和
	
//已经从前i项得到了和sum,然后对于i项之后的进行分支 
int dfs(int i, int sum){
	//如果前n项的都经过计算了,如何sum与k相等,返回1,否则返回0
	if(i == n) return sum==k?1:0;
	//不加上a[i]的情况
	if(dfs(i+1, sum)) return 1;
	//加上a[i]的情况
	if(dfs(i+1, sum+a[i])) return 1;
}

int main(){
	scanf("%d %d",&n,&k);
	for(int i=0; i<n; i++) scanf("%d",&a[i]);
	if(dfs(0,0)){
		printf("Yes\n");
	}else{
		printf("No");
	}
}
3.2 Lake Counting

问题描述
有一个大小为N✖N的园子,雨后积起了水。八连通的积水被认为是连接在一起的。请求出园子里总共有多少个水洼?(八连通指的是下图中相对w的*部分)
限制条件

  • N,M<=100

样例
输入:N=10,M=12

//园子如下图('w'表示积水,'.'表示没有积水)
w........ww.
.www.....www
....ww...ww.
.........ww.
.........w..
..w......w..
.w.w.....ww.
w.w.w.....w.
.w.w......w.
..w.......w.

输出:3
分析
从任意的w开始,不停地把邻接的部分用’.‘代替。1次DFS后与初始的这个w所连接的所有w就都被替换成了’.’,因此直到图中不存在w为止,总共进行DFS的次数就是答案。
代码

#include<stdio.h>
#define MAX_N 100
#define MAX_M 100
int N,M;
char field[MAX_N][MAX_M];  //园子

//现在位置[x,y] 
void dfs(int x,int y){
	//将现在所在位置替换为.
	field[x][y] = '.';
	
	//循环遍历移动的八个方向
	for(int dx = -1; dx <= 1; dx++){
		for(int dy = -1; dy <= 1; dy++){
			//向x方向移动dx,向y方向移动dy,移动结果为(dx,dy)
			int nx = x + dx, ny = y + dy;
			//判断(nx,ny)是不是在园子内,以及是否有积水
			if(0<=nx && nx<=N && 0<=ny && ny<=M && field[nx][ny]=='w') dfs(nx,ny); 
		}
	}
	return ; 
}

int main(){
	//输入 
	scanf("%d %d",&N,&M);
	getchar();
	for(int i=0; i<N; i++){
		for(int j=0; j<M; j++){
			scanf("%c",&field[i][j]);
		}
		//将第一个for循环后面的换行符去掉,否则scanf默认读取换行符 
		getchar();
	}
	//需要DFS的次数	
	int res = 0;
	//开始dfs
	for(int i=0; i<N; i++){
		for(int j=0; j<M; j++){
			if(field[i][j] == 'w'){
				//从有w的地方开始dfs
				dfs(i,j);
				res++; 
			}
		}
	} 
	printf("%d\n",res);
	return 0;
}

宽度优先搜索(BFS)

1.定义

与深度优先搜索不同之处在于搜索的顺序,宽度优先搜索总是先搜索距离初始状态近的状态。也就是说,它是按照开始状态→只需1次转移就可以到达的所有状态→只需2次就可以到达的所有状态→…这样的顺序进行搜索。对于同一个状态,宽度优先搜索只经过一次,因此时间复杂度为O(状态数✖转移的方式)。
宽度优先搜索利用了队列。搜索时首先将初始状态添加到队列里,此后从队列的最前端不断取出状态,把从该状态可以转换到的状态中尚未访问过的部分加入队列,如此反复,直至队列被取空或找到了问题的解。

2.状态转移图

在这里插入图片描述

3.例题
3.1 迷宫的最短路径

问题描述
给定一个大小为N*M的迷宫。迷宫由通道和墙壁组成,每一步可以向邻接的上下左右四格的通道移动。请求出从起点到终点所需的最小步数(起点一定可以到达终点)。
限制条件
N,M<=100
样例
输入:N=10,M=10

//迷宫如下图所示。'#','.','S','G'分别表示墙壁,通道,起点和终点。
#S######.#
......#..#
.#.##.##.#
.#........
##.##.####
....#....#
.#######.#
....#.....
.####.###.
....#...G#

输出:22
分析
宽度优先搜索按照距离开始状态由近及远的顺序进行搜索,因此可以很容易地用来求最短路径、最少操作之类问题的答案。
首先将起点存入队列,然后从队列中取出队头元素,遍历队头元素上下左右四个方向的通道。若其不是终点,则将此点加入队列,并存储从开始到该点的距离。然后处理下一个遍历到的点…直至队列为空。
代码

#include<stdio.h>
#define INF 100000000
#define MAX_N 100
#define MAX_M 100
//输入 
char maze[MAX_N][MAX_M+1];  //表示迷宫的字符串的数组 
int N,M;
int sx,sy;  //起点坐标
int gx,gy;  //终点坐标
int d[MAX_N][MAX_M];   //到各个位置的最短距离的数组

//队列存储结构
int queue_x[MAX_N*MAX_M];  //存储进入队列的横坐标 
int queue_y[MAX_N*MAX_M];  //储进入队列的纵坐标 
int front,rear;

//将坐标(x,y)加入队列queue_x,queue_y
void enQueue(int x, int y){
	queue_x[rear++] = x;
	queue_y[rear++] = y;
}

//将横坐标为x的移出queue_x队列 
int deQueue_x(){
	return queue_x[front++];
}

//将纵坐标为y的移出queue_y队列 
int deQueue_y(){
	return queue_y[front++];
}

//求从{sx,sy}到{gx,gy}的最短路径
//如果无法到达,则是INF
void bfs(){
	//初始化
	front = 0;
	rear = 0;
	 
	//当前遍历到的坐标 
	int current_x,current_y;
	
	//把所有的位置都初始化为INF
	for(int i=0; i<N; i++)
		for(int j=0; j<M; j++)
			d[i][j] = INF;
	
	//将起点加入队列,并把这一地点的距离设置为0
	enQueue(sx,sy);
	d[sx][sy] = 0;
	
	//不断循环, 直到队列的长度为0
	while(maze[current_x=deQueue_x()][current_y=deQueue_y()] != 'G'){
		//遍历左右两面 
		for(int dx=-1,dy=0; dx<=1; dx+=2){
			int x = current_x + dx;
			int y = current_y + dy;
			if(x>=0 && x<N && y>=0 && y<M){  //判断未出界 
				if((maze[x][y]=='G' || maze[x][y]=='.') && d[x][y]==INF){
					d[x][y] = d[current_x][current_y] + 1;
					enQueue(x,y);
				}
			}
		}
		//遍历上下两面 
		for(int dy=-1,dx=0; dy<=1; dy+=2){
			int x = current_x + dx;
			int y = current_y + dy;
			if(x>=0 && x<N && y>=0 && y<M){   //判断未出界 
				if((maze[x][y]=='G' || maze[x][y]=='.') && d[x][y]==INF){
					d[x][y] = d[current_x][current_y] + 1;
					enQueue(x,y);
				} 
			}
		}
	} 
} 

int main(){
	scanf("%d %d",&N,&M);
	getchar();
	for(int i=0; i<N; i++){
		for(int j=0; j<M; j++){
			scanf("%c",&maze[i][j]);
			//确定起点坐标 
			if(maze[i][j] == 'S'){
				sx = i;
				sy = j;
			}
			//确定终点坐标
			if(maze[i][j] == 'G'){
				gx = i;
				gy = j;
			} 
		}
		getchar();
	}
	bfs();
	//输出最终结果 
	printf("%d\n",d[gx][gy]);
	return 0;		
}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值