DFS and BFS

一.最短路径——深度优先搜索

(1)寻找最短路径时,DFS所有路径都要走一遍(缺点),BFS只需要走一条路径。
(2)如果只是找到一条路并非最短路,那么BFS和DFS复杂度是一样的。
(3)以DFS寻找最短路径为例,当搜索图为一张图时(非树,树的话每个点只经过一次,回溯的时候再次回到某个点不代表是第二次哦),并且可走的路径很多,DFS选择不同路径时会在某些点(如右下图A点)重复走,所用时间是极大的(而BFS一个点只走一次)。
如果每个点只走一次,那么广度优先搜索能找到最短路径,深度优先搜索只能得到一条路径,不一定是最短路径
在这里插入图片描述

(4)深度优先搜索有模板,只需稍微改动
(5)特点:回溯
在这里插入图片描述
(6)要标记一下哪些点已经走过了,防止进入下面这样的死循环
在这里插入图片描述

1.题目描述:

在这里插入图片描述

输入样例:
3 3
S*#
**#
#*E
输出样例:
4
问题解决(下面的代码可看做模板):

套模板时要注意改动的地方:
(1)寻找路径的时候,一个点被践踏多次,所以回溯的时候要把这个点标记为0(没走过)
(2)根据题目,有上、下、左、右四个方向

在这里插入图片描述
主函数
int main{
在这里插入图片描述
ans=99999999是为了第一次min的时候,让ans=step

注意:

(1)mpt和vis都需要初始化成0,mpt初始化为0的目的是,待会赋值完后图以外的地方都为0,就形成了一个边界
在这里插入图片描述

(2)输入图时的for循环,i从1开始的,scanf的时候从mpt[i]+1开始的,也就是说不去用图的第0行和第0列。这样的好处是,在dfs函数中,不需要判断dx,dy减1后是否超出边界(变成-1)
(3)在输入的while循环那里不要加!=EOF,根本没用!加了反而出错!不嫌弃的话可以看一下!=EOF的解释
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
 
char grid[101][101];
int vis[101][101];
//大括号内含小括号!!! 
int dir[4][2] = { {-1, 0}, {0, -1}, {0, 1}, {1, 0}};
int ans=9999999;

void DFS(int x, int y,int step)
{
	if(step>ans)  //剪枝 (可以不考虑) 
		return;
	if(grid[x][y]=='E'){
		ans=min(ans,step);
		return;//递归出口--终点
	}
		
	for(int i = 0; i<4; i++)
	{
		int xx = x + dir[i][0];
		int yy = y + dir[i][1];
		//if(xx<0 || xx>=n || yy<0 || yy>=m) continue;	//越界终止本次循环
		
		if((grid[xx][yy] == '*'||grid[xx][yy]=='E')&&vis[xx][yy]==0) {
		    vis[xx][yy]=1;      //关键代码 !! 
			DFS(xx, yy,step+1);	//关键代码!! 
			vis[xx][yy]=0;      //关键代码!!回溯!!! 
		}		    
	}
}
 
int main()
{
	int h,w;
	int i, j;	
	while(scanf("%d%d", &h, &w))
	{
		if(h == 0 && w == 0) break;

		memset(grid, 0, sizeof(grid)); 
		memset(vis, 0, sizeof(vis));
 
		for(i = 1; i <=h; i++) //避免从第0行开始 
			scanf("%s", grid[i]+1);  //避免从第0列开始 
			
		for(i = 1; i <=h; i++){  //从第1行开始搜索
			for(j = 1; j <=w; j++){  //从第1行开始搜索
					if(grid[i][j] == 'S') 
					{ 
					 vis[i][j]=1;  //关键代码!! 
					 DFS(i, j,0);    //关键代码!! 
					 }	
			}
		}
		printf("%d\n",ans);
			
	}
	return 0;
}

如果只是找一条路

下面只给出改动之处的代码。要改两处,
(1)在找到‘E’时,不需要min函数取最小值了。直接记录step并返回
(2)回溯的时候不需要将点标记没走过
(3)不会再有第二条路了,因为第一次找到E的时候,把E标记为1已走过了。
(4)虽然顺着一条路找到了E,但整个DFS过程还是会把所有点都走一遍,然后在return回main函数。



void DFS(int x, int y,int step)
{

	if(grid[x][y]=='E'){
	//	ans=min(ans,step);
		ans=step;
		return;//递归出口--终点
	}
		
	for(int i = 0; i<4; i++)
	{
		int xx = x + dir[i][0];
		int yy = y + dir[i][1];
		//if(xx<0 || xx>=n || yy<0 || yy>=m) continue;	//越界终止本次循环
		
		if((grid[xx][yy] == '*'||grid[xx][yy]=='E')&&vis[xx][yy]==0) {
		    vis[xx][yy]=1;      //关键代码 !! 
			DFS(xx, yy,step+1);	//关键代码!! 
			//vis[xx][yy]=0;      //关键代码!!回溯!!! 
		}		    
	}
}


在这里插入图片描述
实际所有点都走了一遍,顺序是这样的1~9。
在这里插入图片描述

2.题目描述

有@表示石油,@(水平、垂直、对角线)连在一起是一个石油田。求有多少个石油田?
在这里插入图片描述

注意:
(1)根据题目,有8个方向,dir数组需要改
(2)回溯的时候不需要标记为0,因为一个点只走一次

#include<bits/stdc++.h>
using namespace std;
 
char grid[101][101];
int vis[101][101];
//大括号内含小括号!!! 
int dir[8][2] = { {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1} };
 

void DFS(int x, int y)
{
		
	for(int i = 0; i < 8; i++)
	{
		int xx = x + dir[i][0];
		int yy = y + dir[i][1];
		//if(xx<0 || xx>=n || yy<0 || yy>=m) continue;	//越界终止本次循环
		
		if(grid[xx][yy] == '@'&&vis[xx][yy]==0) {
		    vis[xx][yy]=1;      //关键代码 !! 
			DFS(xx, yy);	  //关键代码!! 
		}		    
	}
}
 
int main()
{
	int h,w;
	int i, j;	//循环变量
	int count;	//油田数量计数
	while(scanf("%d%d", &h, &w))
	{
		if(h == 0 && w == 0) break;
		count = 0;
		memset(grid, 0, sizeof(grid));  //char数组也可以用0初始化 
		memset(vis, 0, sizeof(vis));
 
		for(i = 1; i <=h; i++) //避免从第0行开始 
			scanf("%s", grid[i]+1);  //避免从第0列开始 
			
		for(i = 1; i <=h; i++){  //从第1行开始搜索
			for(j = 1; j <=w; j++){  //从第1行开始搜索
					if(grid[i][j] == '@'&&vis[i][j]==0) 
					{ 
					 count++;
					 vis[i][j]=1;  //关键代码!! 
					 DFS(i, j);    //关键代码!! 
					 }	//还有未走过的油田 
			}
		}
			
		printf("%d\n", count);
	}
	return 0;
}

模板总结一下
全局变量有:图、记录图、方向
main函数负责:memset初始化,输入,甚至搜索。
DFS函数负责:不需要递归出口?!;然后就是各个方向的找寻。
在main函数和DFS函数中都有关键代码,干两件事,一件是标记已走,一件是继续递归(可看做堆栈,对比着BFS的队列来看)

(3)如果不去掉第0行和第0列的话,需要在dfs里判断是否超出上、下、左、右边界


void DFS(int x, int y)
{
	......
	for(int i = 0; i < 8; i++)
	{
		xx = x + dir[i][0];
		yy = y + dir[i][1];
		if(xx<0 || xx>=n || yy<0 || yy>=m) continue; //越界终止本次循环!!!!
		if(grid[xx][yy] == '@'&&vis[xx][yy]==0) {
			DFS(xx, yy);		
		}		    
	}
}
 
int main()
{
	while(scanf("%d%d", &h, &w))
	{
		......
 
		for(i = 0; i <h; i++) 		//从第0行开始!!!!! 
			scanf("%s", grid[i]);  //从第0列开始 !!!!! 
			
		for(i = 0; i <h; i++){    //从第0行开始 !!!! 
			for(j = 0; j <w; j++){  //从第0列开始 !!!! 
			......	
			}
		}
			

}

如果不另外用一个二位数组标记是否已走过的话,思路会更简单,而且不容易出错。

# include<bits/stdc++.h>
using namespace std;
/*
一个点只走一次,所以DFS和BFS的复杂度都符合要求。
思路:走过的油田改成'*' ,这样就不需要另外一个二维数组记录哪里走过没有了。 
*/ 

int h,w;
char oil[100][100];
int dir[8][2]={{1,0},{0,1},{-1,0},{0,-1},{1,1},{-1,-1},{1,-1},{-1,1}};
int ct=0;

/*作用:将发现的那片油田抹除*/
void dfs(int x,int y){
	
	for(int i=0;i<8;i++){ //8个方向都来一遍 
		int dx=x+dir[i][0];
		int dy=y+dir[i][1];
		if(oil[dx][dy]=='@'){
			oil[dx][dy]='*';
        	dfs(dx,dy);
		}
			
	}
}

/*作用:寻找是否还有油田*/
int main(){
	memset(oil,0,sizeof(oil)); //地图初始化
	while(scanf("%d%d",&h,&w)){
		ct=0;
		if(h==0&&w==0) break;
		
		for(int i=1;i<=h;i++){  //一行一行的输入
			scanf("%s",oil[i]+1); 		
		}
		for(int i=1;i<=h;i++){
			for(int j=1;j<=w;j++){
				if(oil[i][j]=='@') { //一旦发现有石油
				    ct++;
				    oil[i][j]='*';  //关键代码
					dfs(i,j);  //关键代码
				}
			}
		}
		printf("%d",ct);	
	}
	
return 0;
}

4.螺旋数字

给定矩阵的大小,输出下面这样的螺旋数字。
在这里插入图片描述

思路:

很明显每一个结点对于方向的选择是有个优先级排序的,先往下,下面没有了再往右,右边没有了,再往上,上面没有了,再往左,左边没有了,又回到最初的往右。

一开始,我想的是设计“下,右,上,左”的优先级,

int dir[4][2] = { {1, 0}, {0, 1}, {-1, 0}, {0, -1} };  //下,右,上,左的顺序 

	for(int i = 0; i<4; i++)
	{
		int xx = x + dir[i][0];
		int yy = y + dir[i][1];
	}

但这样的思路有一个破绽,看上图中走到13时,按照我们的“下,右,上,左”的优先级,13应该往下走,而我们想让他往右走。
所以优先级应该是“下,右,上,左,右”,注意,左和下同时可以走时,左的优先级是大于下的。所以这是个循环优先级!(怎么跑到微机的概念上去了)
在这里插入图片描述
从左到下的循环我用了如下代码来实现:

if(grid[x+1][y]==333&&grid[x][y-1]==333){
			xx=x;
			yy=y-1;
		}

翻译过来就是,当左边和下边都可以走时,选择走左边。

下面给出完整代码。

#include<bits/stdc++.h>
using namespace std;
 
int grid[101][101];

int dir[4][2] = { {1, 0}, {0, 1}, {-1, 0}, {0, -1} };  //下,右,上,左的顺序 
int cnt=1;
int h,w;
void DFS(int x, int y)
{
	for(int i = 0; i<4; i++)
	{
		int xx = x + dir[i][0];
		int yy = y + dir[i][1];
		
		if(grid[x+1][y]==333&&grid[x][y-1]==333){
			xx=x;
			yy=y-1;
		}
		
		if(grid[xx][yy] == 333) {
			cnt++;
			grid[xx][yy]=cnt; //关键代码!!
//			for(int m=1;m<=h;m++){
//				for(int n=1;n<=w;n++){
//					cout<<grid[m][n]<<" ";
//				}
//				cout<<endl;	
//			}
//			cout<<"--------------------"<<endl;
			DFS(xx, yy);	//关键代码!! 
		}		    
	}
}
 
int main()
{
	
	int i, j;	
	while(scanf("%d%d", &h, &w))
	{
		if(h == 0 && w == 0) break;

		memset(grid, 0, sizeof(grid)); 
 
		for(i = 1; i <=h; i++) //避免从第0行开始 
			for(int j=1;j<=w;j++)
				grid[i][j]=333;
		grid[1][1]=1; //关键代码!!
		DFS(1,1);   //从grid[1][1]开始 //关键代码!!
		for(i = 1; i <=h; i++) {
			for(int j=1;j<=w;j++)
				printf("%d ",grid[i][j]);
			cout<<endl;
		}
			
		
			
	}
	return 0;
}




二、广度优先搜索

(1)广度优先特点在于,从一个点出发层层扩散出去。所以一打眼看到墨水染色这个题的时候就应该想到用BFS。要实现这个层层扩散就要借用队列先进先出的思想。所以BFS一般都用队列来实现
(2)有模板哦!

1.题目描述

还是上面那个迷宫
模板如下

#include<bits/stdc++.h>
using namespace std;

struct node{
	int x;
	int y;
	int step;
}; 

const int MAX_N = 100+5;
char grid[MAX_N][MAX_N];
int vis[MAX_N][MAX_N]; 
int dir[4][2]={{0,1},{0,-1},{-1,0},{1,0}}; //上、下、左、右四个方向
int ans = -1;

int BFS(int sx,int sy){
	//声明队列 
	queue<node> q;
	//起点入队 
	q.push(node{sx,sy,0});
	
	/*--------BFS---------*/
	while(!q.empty()){
		int size = q.size();
		for(int i=1; i<=size; i++){
			node now = q.front();
			q.pop();
			if(grid[now.x][now.y]=='E'){  //找到终点了! 
				ans = now.step;
				break;
			}
			for(int j=0; j<4; j++){
				int nx = now.x+dir[j][0];
				int ny = now.y+dir[j][1];
				if((grid[nx][ny]=='*'||grid[nx][ny]=='E')&&vis[nx][ny]==0){
					q.push({nx,ny,now.step+1}); //关键代码!!! 
					vis[nx][ny]=1; //关键代码 !!! 
				}
			}
		}
	}
	
	/*-----结果输出----*/
	printf("至少需要%d步\n", ans);
} 

int main(){
	int h,w;
	int i,j;
	while(scanf("%d%d",&h,&w)){
		if(h==0&&w==0) break;
		
		memset(grid, 0 ,sizeof(grid));
		memset(vis, 0, sizeof(vis));
		
		/*-----绘制地图-----*/
		for(i=1; i<=h; i++){  //避免从第0行开始 
			scanf("%s",grid[i]+1);  //避免从第0列开始 
		}
		/*-------Action!----*/
		for(i=1; i<=h; i++){
			for(j=1; j<=w; j++){
				if(grid[i][j]=='S'){
					vis[i][j]=1;  //关键代码!!! 
					BFS(i,j);	//关键代码(不太算!!! 
				}
			}
		}
	}
	return 0;
}

模板总结:
全局变量:多了一个struct node
main函数:一样
BFS函数:创建队列;起点入队;标记起点已走过;while(!q.empty()),先出队,判断出队的这个是最终结果吗?(对应于DFS的递归出口),再各个方向入队
关键代码也是干两件事

2.题目描述

输入一个m*n的矩阵,矩阵中的元素包含0、1、2(其中0表示什么都没有,1表示白纸,2表示墨水),每秒墨水会向四周(上下左右)进行染色,被染色的纸也会对周围的纸进行染色。试问,对于输入的矩阵,多久才能完全染色?如果最终不能完全染色,输出-1.
解析:自己手动进行染色,并模拟队列,会发现如果完全染色,队列左后一个元素所带的时间step就是总用时

# include<bits/stdc++.h>
using namespace std;
/*不用标记走没走过,因为染过了的就是2*/
char grid[100][100];
int h, w;
int dir[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
struct node{
	int x;
	int y;
	int step;
}; 

int BFS(){
	int ans=0;
	queue<node> q;  //创建队列 
	for(int i=1;i<=h;i++){
		for(int j=1;j<=w;j++){
			if(grid[i][j]=='2')
				q.push({i,j,0});  //把一开始就是绿色的入队 
		}
	}
	
	while(!q.empty()){
		int size=q.size(); //当前队列有多少个
		for(int i=1;i<=size;i++){
			node now=q.front();
			q.pop();
			ans=now.step;
			for(int k=0;k<4;k++){
				int x=now.x+dir[k][0];
				int y=now.y+dir[k][1];
				if(grid[x][y]=='1'){
					grid[x][y]='2';  //关键代码!!染色 
					q.push({x,y,now.step+1});  //关键代码 !! 
				}
					
			}
		}		
	}
	
	for(int i=1;i<=h;i++){
		for(int j=1;j<=w;j++){
			if(grid[i][j]=='1')
				ans=-1;  //未完全染色 
		}
	}
	return ans;
}

int main(){
	/*-------初始化-------*/

	while(scanf("%d%d",&h,&w)){
		if(h==0&&w==0) break;
		memset(grid,0,sizeof(grid));
		for(int i=1;i<=h;i++){
			scanf("%s",grid[i]+1);
		}
		/*染色*/
		int result=BFS();
		printf("%d\n",result);
	}

	return 0;
}
	





三、例题们

1.被围绕的区域

在这里插入图片描述
解析:
思路是从四个边界出发,找到O后开始深度搜索,和这个O连着的所有O都属于在边界上,先用#标记。四个边界搜索完后,图中剩下的O是真正不挨着边界的O,把他们变成X。最后把#变成O。

(1)用DFS求解:

注意:
①由于LeetCode上给定了图board,行和列都是从0开始的,所以必须进行越界处理

class Solution {
public:
    void solve(vector<vector<char>>& board) {
        int h=board.size();
        int w=board[0].size();
        /*---沿着4个边界扫描,看有没有“O”了?----*/
        for(int k=0;k<h;k++){
            if(board[k][0]=='O')
                dfs(k,0,board);
            if(board[k][w-1]=='O')
                dfs(k,w-1,board);
        }
        for(int k=0;k<w;k++){
            if(board[0][k]=='O')
                dfs(0,k,board);
            if(board[h-1][k]=='O')
                dfs(h-1,k,board);
        }
        /*---------0先变X------------*/
        for(int i=0;i<h;i++){
            for(int j=0;j<w;j++){
                if(board[i][j]=='O')
                    board[i][j]='X';
            }
        }
        /*-------#再变回O-----------*/
        for(int i=0;i<h;i++){
            for(int j=0;j<w;j++){
                if(board[i][j]=='#')
                    board[i][j]='O';
            }
        }
    }

    void dfs(int x,int y, vector<vector<char>>& board){
        int dir[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
        board[x][y]='#'; //走过的用#标记,关键代码!!!!!
        for(int i=0;i<4;i++){
            int dx=x+dir[i][0];
            int dy=y+dir[i][1];
            if(dx<0||dx>=board.size()||dy<0||dy>=board[0].size()) //越界处理
                continue;
            if(board[dx][dy]=='O'){
                dfs(dx,dy,board);
            }
        }
    }
};

DevC++版本:

#include<bits/stdc++.h>
using namespace std;

char grid[100][100];
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
void DFS(int x,int y){
	for(int i=0;i<4;i++){
		int xx=x+dir[i][0];
		int yy=y+dir[i][1];
		if(grid[xx][yy]=='O'){
			grid[xx][yy]='#';
			DFS(xx,yy);
		}
	}
}

int main(){
	int h,w;
	cin>>h>>w;
	memset(grid,0,sizeof(grid));  //初始化 
	for(int i=1;i<=h;i++){  //输入 
		scanf("%s",grid[i]+1);
	}
	/*-----寻找边界上的O点,改为#-----*/
	for(int i=1;i<=h;i++){  //第一列 
		if(grid[i][1]=='O'){
			grid[i][1]='#';
			DFS(i,1);
		}
			
	}
	for(int i=1;i<=h;i++){  //最后一列 
		if(grid[i][w]=='O'){
			grid[i][w]='#';
			DFS(i,w);
		}
			
	}
	for(int i=1;i<=w;i++){  //第一行 
		if(grid[1][i]=='O'){
			grid[1][i]='#';
			DFS(1,i);
		}
		
	}
	for(int i=1;i<=w;i++){  //最后一行 
		if(grid[h][i]=='O'){
			grid[h][i]='#';
			DFS(h,i);
		}
			
	}
	/*------寻找内部的O点,改为X-----*/
	for(int i=1;i<=h;i++){
		for(int j=1;j<=w;j++){
			if(grid[i][j]=='O')
				grid[i][j]='X';
		}
	} 
	/*-----把边界的#改回O点------*/
	for(int i=1;i<=h;i++){
		for(int j=1;j<=w;j++){
			if(grid[i][j]=='#')
				grid[i][j]='O';
		}
	} 
	/*--------输出-------*/
	for(int i=1;i<=h;i++){
		for(int j=1;j<=w;j++){
			cout<<grid[i][j]<<' ';
		}
		cout<<endl;
	}
	return 0;
}

在这里插入图片描述

2.扫雷游戏

在这里插入图片描述

这里用的DFS,但报出了栈溢出的错误。思路是正确的,以后再回来看看哪里错了吧

class Solution {
public:
/*8个方向*/
    
    vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {
        int dir[8][2]={{1,0},{0,1},{-1,0},{0,-1},{1,1},{-1,-1},{1,-1},{-1,1}};
        if(board[click[0]][click[1]]=='M'){
            board[click[0]][click[1]]='X';
            return board;
        }

        //if(board[click[0]][click[1]]=='E')
        int count=0;
        for(int i=0;i<8;i++){        
            int x=click[0]+dir[i][0];
            int y=click[1]+dir[i][1];
            if(x<0||x>=board.size()||y<0||y>=board[0].size())
                    continue;
            if(board[x][y]=='M'){
                count++;
            }
        }
        if(count>0){//与地雷相邻呀
            board[click[0]][click[1]]=count+48; //????
            return board;
        }else{
            board[click[0]][click[1]]='B';
            for(int i=0;i<8;i++){ 
                vector<int> c={0,0};   
                c[0]=click[0]+dir[i][0];
                c[1]=click[1]+dir[i][1];
                if(c[0]<0||c[0]>=board.size()||c[1]<0||c[1]>=board[0].size())
                    continue;
                updateBoard(board,c);
            }
        }           
    return board;
    }
};
  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值