简单教会你入门DFS

递归

在入门dfs之前,首先需要知道递归是什么?

递归简单地说就是在自定义函数中调用自己  

请看下面这个程序以及运行结果来体会一下递归的执行顺序 

#include<stdio.h>

void Recursion(int cnt)
{
	if (cnt>0)
	{
		printf("假嘟+%d ",cnt);
		Recursion(cnt - 1);
		printf("尊嘟+%d ",cnt);
	}
	else
	{
		printf("%d ", cnt);
	}
}
int main()
{
	Recursion(3);
	return 0;
}

运行结果如下

                           假嘟+3 假嘟+2 假嘟+1 0 尊嘟+1 尊嘟+2 尊嘟+3 

大家可以先悟一下其中的逻辑。(数字表示递归的层数)

506630737c264cf5a4c8ab246ad94479.png

这是代码执行到0的时候的逻辑。不难看出,当代码遇到Recursion函数中的Recursion函数的时候 会返回到Recursion函数,而在遇到之前,代码的逻辑都是正常的,会输出“假嘟+%d”

从结果可以看出,依次输出了数字3,2,1。而cnt一开始是3,也验证了我所给出的执行顺序。

fc3cdadf563642c5bf835b6f2f6f5307.png 

在第四层中,我们发现,程序没有进入cnt>0的块中,而是输出了cnt,因此,没有再碰到Recursion了。因此,这便是递归的最深层。在第四层结束后,便返回到第三层,还是从Recursion出来,继续将第三层的剩余代码执行完,第三层中cnt=1,于是输出了“尊嘟+1” 。接着返回到第二层,直到第一层执行结束。于是最后输出了“尊嘟+1 尊嘟+2 尊嘟+3”。从数字的输出顺序,可以得知:

1.递归是从最深层开始一层一层地返回的

2.不同层递归中的变量没有关系,如每层的cnt值都不同

 DFS

DFS叫做深度优先搜索算法,是一种盲目的算法,下面我简单地说一下它的理解重点

1.顺序是一条路走到黑,不撞南墙不回头

2.所有的合法位置都会搜索到

3.我们不必知道它搜索到了哪里,只需要告诉程序有没有搜索过某位置,搜索过便立马去下一位置,未搜索过便搜索

打个不恰当的比喻,我们就是老板,DFS就是员工,员工每次工作前给老板打报告,老板说要干就干,不要干就不干,实际上,老板会让员工干完所有的工作。 

例题助理解

这是洛谷P1548题

咱直接暴搜不走数学法!!!


设有一个N×M方格的棋盘(1≤N≤100,1≤M≤100)

求出该棋盘中包含有多少个正方形、多少个长方形(不包括正方形)。

例如:当N=2,M=3时: 

e0397ccb07a94bcbe075de0fcf119c3a.png

正方形的个数有8个:即边长为1的正方形有6个;

边长为2的正方形有2个。

长方形的个数有10个:

2×1的长方形有4个 

ab819b8a8535a519a1fa90c90937e14b.png

1×2的长方形有3个: 

d830a9cf5ff615c7f26b0c4cc90810ff.png

3×1的长方形有2个: 

55183f5de7b7420eaa9f6211e8d9e829.png

3×2的长方形有1个: 

3b6500aeddedafaf555c2bd5b68f36d7.png

如上例:输入:2,3

输出:8,10


暴搜思路准备 

首先,先思考如何去确定一个矩形。很简单,任选两个不在同一直线上的点便确定了一个矩形。然后如果两点之间横纵坐标分别的差的绝对值是相等的,那么就是正方形,否则就是矩形。

因此,我们需要定义一个“基点”,一个“动点”。基点保持不动,为了避免重复,动点将在基点右下方,dfs将这些点都搜索一遍,然后分析形成的图形是矩形还是正方形。

 

1ca2199ccc77400db6d7e0c07422dc3e.png

红点为基点,蓝色的点是动点可以去的任意位置。

代码及分析

 

全局变量:
int cnt1 = 0, cnt2 = 0;//cnt1为长方形,cnt2为正方形

main中:
int M, N;
cin >> N >> M;//N行M列
int visited[101][101];//创建visited数组表示是否访问过
vector<vector<int>> coor(N+1,vector<int>(M+1, 0));//创建N+1行,M+1列的二维数组
    

再自定义个Init函数将visited全部初始化

void Init(int visited[101][101])
{
	for (int i = 0; i < 101; i++)
	{
		for (int j = 0; j < 101; j++)
		{
			visited[i][j] = 0;
		}
	}
}

然后我们写dfs函数

void dfs(int n, int m,int visited[101][101], int i, int j, int N, int M)
{
	if (n<0 || m<0 || n>N || m>M)//n,m分别表示动点横纵坐标
	{                            //i,j表示基点的横纵坐标
		return;                  //N,M表示了图形的边界
	}
/*
上面代码是判断是否到达边界,也就是南墙,如果撞到南墙,便返回到递归的上一层
*/
	if (visited[n][m]==1)
	{
		return;
	}
/*
上面代码是判断是否访问过该点,如果访问过,返回到递归的上一层。若没有,则访问
*/
	visited[n][m] = 1;//访问后,便将visited[n][m]标记为1,来代表“已访问”
	if (n - i > 0 && m - j > 0)//确保动点在基点的右下方
	{
		if (n - i == m - j)//判断正方形
		{
			cnt2 += 1;//正方形总数加1
		}
		else
		{
			cnt1 += 1;//长方形总数加1
		}
	}
	dfs(n - 1, m, visited, i, j, N,M);//将动点上移,进而搜索
	dfs(n + 1, m, visited, i, j, N,M);//动点下移
	dfs(n, m - 1, visited, i, j, N,M);//动点左移
	dfs(n, m + 1, visited, i, j, N,M);//动点右移
}

初看4个dfs函数放在一起,我们可能会一脸懵逼,并试图去人脑跑一边代码,窥探代码的逻辑。

我的回答是:没必要!!!

还记得我说我们是老板,dfs是员工吗?我们不必管dfs怎么干活,我们只要见到没干的活,就甩给dfs去干就行了。

因而,最后dfs帮我们把所有的动点可能出现的位置都搜索了一遍。

这就结束了吗?并没有。基点也需要改变,不同的基点有着不同的动点范围。因此我们在主函数中调用dfs函数的时候,需要用循环套一下,给dfs不同的基点位置。为了图方便,我们把所有位置都当作基点可能出现的位置。记得在基点更换前,需要将visited初始化,对于下一个基点来说,动点都没有访问过哦。

for (int j = 0; j < M; j++)
{
	for (int i = 0; i < N; i++)
	{
		dfs(0, 0,visited,i,j, N,M);
		Init(visited);
	}
}

 最后我们输出一下cnt1,cnt2即可得出答案。思路非常简单,代码也很少。

完整代码

#include<iostream>
#include<vector>
using namespace std;
int cnt1 = 0, cnt2 = 0;//cnt1为长方形,cnt2为正方形
void dfs(int n, int m,int visited[101][101], int i, int j, int N, int M)
{
	if (n<0 || m<0 || n>N || m>M)
	{
		return;
	}
	if (visited[n][m]==1)
	{
		return;
	}
	visited[n][m] = 1;
	if (n - i > 0 && m - j > 0)
	{
		if (n - i == m - j)
		{
			cnt2 += 1;
		}
		else
		{
			cnt1 += 1;
		}
	}
	dfs(n - 1, m, visited, i, j, N,M);
	dfs(n + 1, m, visited, i, j, N,M);
	dfs(n, m - 1, visited, i, j, N,M);
	dfs(n, m + 1, visited, i, j, N,M);
}
void Init(int visited[101][101])
{
	for (int i = 0; i < 101; i++)
	{
		for (int j = 0; j < 101; j++)
		{
			visited[i][j] = 0;
		}
	}
}
int main()
{
	int M, N;
	cin >> N >> M;//N行M列
	int visited[101][101];
	vector<vector<int>> coor(N+1,vector<int>(M+1, 0));
	for (int j = 0; j < M; j++)
	{
		for (int i = 0; i < N; i++)
		{
			dfs(0, 0,visited,i,j, N,M);
			Init(visited);
		}
	}
	cout << cnt2 <<" " << cnt1;
}

感谢阅读,点点关注点点赞~~~

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值