#2020寒假集训#搜索入门(BFS和DFS)代码笔记

wow好高级的名字!!!害,其实就是一种高级的遍历啦~
先来康康全拼(◕ᴗ◕✿)
【BFS】全拼Breadth First Search 常用于迷宫问题、最短路问题
【DFS】全拼Depth First Search 有些题用DFS会TLE,但用BFS就能AC,时间复杂度较难分析

在这里插入图片描述
再来康康一般格式(◕ᴗ◕✿)
【BFS】
(bool)ok函数:判断遍历到的点位是否符合题意,分别返回true和false
(int)sign变量:标记点位是否走/满足题意过
(int)ans变量:计算满足题意的点位数
(void)bfs函数:让main函数传入的值入队(这个值一般是全局变量定义,bfs无参数)
初始化sign变量+定义队列+ans开始计数+循环记录队首元素调用ok&队首出队(队列为空结束)
(int)main函数:传出初始位置+bfs()+打印输出结果

【DFS】
(bool)ok函数:判断遍历到的点位是否符合题意,分别返回true和false
(int)sign变量:标记点位是否走/满足题意过
(int)ans变量:计算满足题意的点位数
(void)dfs函数:让main函数传入的值入队(dfs有参数,因为要递归)
判断点位合法性(不合法直接return结束)+循环调用ok返回true则递归+每次记录走到的sign
(int)main函数:初始化sign变量+循环遍历走过的被sign函数标记的点位+ans计数+打印输出结果

@其实通俗些来讲@
【广度优先搜索-横着看】顺序是A-B-C-D-E-F-G-H-I-J(先找兄弟结点)
【深度优先搜索-竖着看】顺序是A-B-D-G-D-H-I-D-B-A-C-E-J-E-C-F-C-A(先找子孙节点,勿忘倒回去)
@那么用代码实现的时候@
BFS广度】一般采用queue队列,高级些就是Priority_queue优先队列
DFS深度】一般采用递归或者

我们来看几道例题叭(◕ᴗ◕✿)
Red and Black
HZNU19training题源

Background
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)

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).

Sample Input
6 9
…#.
…#





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

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

Sample Output
45
59
6
13

英文题面真的好痛苦呢,来来来,快欣赏下网上找的中文题面叭٩(๑>◡<๑)۶
在这里插入图片描述

我们用两种做法都做一遍叭(◕ᴗ◕✿)

- BFS广度优先搜索(又称宽度优先搜索or横向优先搜索)

#include<stdio.h>
#include<utility>
#include<string.h> 
#include<queue>
using namespace std;
#define PII pair<int,int>//PII就是个宏定义代号啦 
#define x first//define前面要带#,后面不能加; 
#define y second//define格式:#define 标识符 字符串 
const int N=1e2+10;
int n,m,sx,sy;
char s[N][N];
int use[N][N];//记录某个各自是否已经走过,1走过;0未走过 
int Move[4][2]=//实现四个方向的位移,注意不要用move全小写命名,可能报错
{
	{-1,0},
	{1,0},
	{0,1},
	{0,-1},
};
bool ok(int x,int y)//判断可走性 
{
	if(x<1||x>n||y<1||y>m) return false;//出界了不可走 
	if(s[x][y]=='#') return false;//红格子(#)不可走 
	if(use[x][y]) return false;//走过了不可走 
	return true;//前三条的return都没进行,那么就说明可走 
}
void bfs()//广度优先搜索(也称作 宽度or横向 优先搜索) 
{
	memset(use,0,sizeof(use));
	queue<PII> que;
	que.push(make_pair(sx,sy));
	use[sx][sy]=1;
	int res=0;
	while(!que.empty())//只要瘦新元素放进来,就能继续循环走位 
	{
		PII u=que.front();//u是队列的首元素 
		que.pop();//移除队列首元素,但注意,已经记录在u里面了喔 
		res++;
		for(int i=0;i<4;i++)
		{
			int nx=u.x+Move[i][0];
			/*
			Move的第二个下表中,0表示x坐标,1表示y坐标 
			i的1 2 3 4分别是往左 右 上 下 
			*/
			int ny=u.y+Move[i][1];
			if(ok(nx,ny))
			{
				use[nx][ny]=1;
				que.push(PII(nx,ny));//把新元素放到队首 
			}
		}
	}
	printf("%d\n",res); 
}
int main()
{
	while(~scanf("%d %d",&m,&n),m+n)
	{
		for(int i=1;i<=n;i++)
		{
			scanf("%s",s[i]+1);
			/*
				s[i]+1即本来是从s[i][0]开始存数据,现在从s[i][1]开始存数据 
				其实就是为了后续每个格子都能用直观的行列数做下标
				第一行第一列的数就用1,1;而不是0,0
				调用起来顺手些、舒服些 
			*/
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				if(s[i][j]=='@')//找到开始位置 
				{
					sx=i;
					sy=j;
				}
			}
		}
		bfs();//sx和xy是开始位置的横纵坐标 
	}
	return 0;
}

- DFS深度优先搜索(又称宽度优先搜索or横向优先搜索)

#include<stdio.h>
#include<utility>
#include<string.h> 
using namespace std;
#define x first//define前面要带#,后面不能加; 
#define y second//define格式:#define 标识符 字符串 
const int N =1e2+10;
int m,n;//m是列数;n是行数 
char s[N][N];
int use[N][N];//记录某个各自是否已经走过,1走过;0未走过 
int Move[4][2]=//定实现四个方向的位移 
{
	{-1,0},
	{1,0},
	{0,1},
	{0,-1},
};
bool ok(int x,int y)//判断可走性 
{
	if(x<1||x>n||y<1||y>m) return false;//出界了不可走 
	if(s[x][y]=='#') return false;//红格子(#)不可走 
	if(use[x][y]) return false;//走过了不可走 
	return true;//前三条的return都没进行,那么就说明可走 
}
void dfs(int x,int y)//深度优先搜索 
{
	if(s[x][y]=='#') return;
	/*
		void类型函数强制终止运行下面的语句时,可直接写return
		void类型的函数无返回值
	*/
	if(use[x][y]) return;
	use[x][y]=1;
	for(int i=0;i<4;i++)
	{
		int nx=x+Move[i][0];
		/*
			Move的第二个下表中,0表示x坐标,1表示y坐标 
			i的1 2 3 4分别是往左 右 上 下 
		*/
		int ny=y+Move[i][1];
		if(ok(nx,ny)) dfs(nx,ny);
		/*
			递归走格子,这个格子能走就从这个格子开始再找能往哪走
			递归回去判断新use记不记1
		*/
	}
}
int main()
{
	while(~scanf("%d %d",&m,&n),m+n)//m+n确保它俩不同时为零,否则程序结束 
	{
		memset(use,0,sizeof(use));
		for(int i=1;i<=n;i++)
		{
			scanf("%s",s[i]+1);
			/*
				s[i]+1即本来是从s[i][0]开始存数据,现在从s[i][1]开始存数据 
				其实就是为了后续每个格子都能用直观的行列数做下标
				第一行第一列的数就用1,1;而不是0,0
				调用起来顺手些、舒服些 
			*/
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				if(s[i][j]=='@') dfs(i,j);
				//遍历找到@就找到了起始位置,开始深度优先搜索 
			}
		}
		int res=0;//res记录走过的格子有几个
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				res+=use[i][j];//走过的是1,没走过的是0 
			}
		}
		printf("%d\n",res);
	}
	return 0;
}

一般情况下,DFS(深度)比BFS(广度)用起来简单
毕竟BFS写queue(对列)还要用到pair,语法上难度也大一些嘛o(´^`)o
那么我们用DFS再来做一题叭()☆

N皇后问题
HZNU19training题源

Background
在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。
你的任务是,对于给定的N,求出有多少种合法的放置方法。

Input
共有若干行,每行一个正整数N≤10,表示棋盘和皇后的数量;如果N=0,表示结束。

Output
共有若干行,每行一个正整数,表示对应输入行的皇后的不同放置数量。‘

Sample Input
1
8
5
0

Sample Output
1
92
10

- DFS深度优先搜索(又称宽度优先搜索or横向优先搜索)

这个DFS虽然会比BFS简单,但是这个神奇的递归真的差点懵逼!!!呜呜呜ヾ(•ω•`。)

#include<stdio.h>
#include<string.h>
using namespace std;
const int N=1e2+10;
int n,sign[N][N],res,ans[N];
/*
	n为输入的棋盘尺寸、皇后数量
	ans数组记录输入每个n的情况的答案 
	sign数组在标记棋盘位置上有没有皇后,有皇后了就要标记1
	当新开一种情况遍历的时候,注意清空g数组中会影响之后判断的标记 
*/
bool ok(int x,int y)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(!(x==i&&y==j))
			{//x,y是传入的位置,遍历其他位置,判断皇后能不能待在x,y这个位置 
				if(sign[i][j]==1)
				{
					if(i==x) return false;//同行 
					if(j==y) return false;//同列 
					if(i+j==x+y) return false;//同撇斜线 
					if(i-j==x-y) return false;//同捺斜线 
				}
			}
		}
	}
	return true;
}
void dfs(int cur)
{
	if(cur==n+1)//如果cur已经超出了棋盘维度,说明每一行都有皇后了,完成任务 
	{
		res++;//实现要求,算作一种摆放可能
		/*
			如果某一行每个位置都放不了皇后
			那么i的遍历就会结束,函数结束
			但注意只是那一行的函数结束
			它只是说明第一行的某一个位置开始,完成不了皇后的放置 
			只有当第一行每个位置都确认完毕函数才真正结束
			不然只是递归内部有出口了 
		*/ 
		return;//强制退出函数 
	}
	for(int i=1;i<=n;i++)
	{
		if(ok(cur,i))//判断地cur行第i列放皇后可不可行,找到可行的那一列 
		{
			sign[cur][i]=1;
			/*
				从这个ok过了的位置开始放,那么得标记1说明有皇后了
				在一个初始i产生的一系列递归里面,会一层层地多出标记1的位置
				调用ok函数的时候就要判断是否冲突,判断可行性 
			*/
			dfs(cur+1);
			/*
				一行找到皇后位置了就找下一行,看看下一行还有没有皇后能待的位置
				在新的一行依旧要从左到右一整行都试一遍 
			*/
			sign[cur][i]=0;
			/*
				一个初始i结束了,从下一个初始i开始
				因为是新的分支情况,一切都得重来
				但其实对于第n行,也可能有m种情况
				这个时候就相当于,新开了一个m种之一的情况
				那么上一个m之一的情况以后产生的标记都得清空 
			*/
		}
	}
}
int main()
{
	memset(ans,-1,sizeof(ans)); 
	while(scanf("%d",&n),n)
	{//棋盘n*n,皇后有n个,n==0时终止
		if(ans[n]!=-1) 
		{//重复输入同一个n的时候,节约时间直接调取答案,不要再遍历一遍啦!!!
			printf("%d\n",ans[n]);
			continue;
		}
		memset(sign,0,sizeof(sign));//给数组初始化的时候,sizeof括号里填写数组名 
		res=0;
		dfs(1);
		/*
			从第一行开始遍历,一行一行找皇后,在函数内部递归(DFS)
			用res在函数内部求和即可 
		*/
		ans[n]=res;
		printf("%d\n",ans[n]);
	}
	return 0;
}
发布了22 篇原创文章 · 获赞 0 · 访问量 412

猜你喜欢

转载自blog.csdn.net/qq_46184427/article/details/103997899