递归
在入门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
大家可以先悟一下其中的逻辑。(数字表示递归的层数)
这是代码执行到0的时候的逻辑。不难看出,当代码遇到Recursion函数中的Recursion函数的时候 会返回到Recursion函数,而在遇到之前,代码的逻辑都是正常的,会输出“假嘟+%d”。
从结果可以看出,依次输出了数字3,2,1。而cnt一开始是3,也验证了我所给出的执行顺序。
在第四层中,我们发现,程序没有进入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时:
正方形的个数有8个:即边长为1的正方形有6个;
边长为2的正方形有2个。
长方形的个数有10个:
即
2×1的长方形有4个
1×2的长方形有3个:
3×1的长方形有2个:
3×2的长方形有1个:
如上例:输入:2,3
输出:8,10
暴搜思路准备
首先,先思考如何去确定一个矩形。很简单,任选两个不在同一直线上的点便确定了一个矩形。然后如果两点之间横纵坐标分别的差的绝对值是相等的,那么就是正方形,否则就是矩形。
因此,我们需要定义一个“基点”,一个“动点”。基点保持不动,为了避免重复,动点将在基点右下方,dfs将这些点都搜索一遍,然后分析形成的图形是矩形还是正方形。
红点为基点,蓝色的点是动点可以去的任意位置。
代码及分析
全局变量:
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;
}
感谢阅读,点点关注点点赞~~~