赞
踩
前言:深入学习了两天dfs算法相关的知识,根据自己的学习认识总结一下:
常见的dfs算法题有如下几类:
a:判断 “是否存在” 的问题,遍历所有的点直接判断;
b:要求能到达的 “数量” 问题;
B:例题
典型的例题1——数量问题:红与黑_牛客题霸_牛客网 (nowcoder.com)
- #include<stdio.h>
- int m,n,i,j;
- char a[30][30];
- int dfs(int x,int y)
- {
- if(x>=m||x<0||y>=n||y<0)//边界
- {
- return 0;
- }
- else if(a[x][y]=='#')//限制条件
- {
- return 0;
- }
- else//正常搜索条件
- {
- a[x][y]='#';//标记不能回头
- return 1+dfs(x-1,y)+dfs(x+1,y)+dfs(x,y-1)+dfs(x,y+1);//递归公式
- }
- }
- int main()
- {
- while(scanf("%d%d",&m,&n)!=EOF)
- {
- for(i=0;i<m;i++)
- {
- scanf("%s",a[i]);
- }
- for(i=0;i<m;i++)
- {
- for(j=0;j<n;j++)
- {
- if(a[i][j]=='@')
- {
- printf("%d\n",dfs(i,j));
- break;
- }
- }
- }
- }
- }
原题目描述
一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由 n∗n 的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不能通行。
同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上,Extense想要从点A走到点B,问在不走出迷宫的情况下能不能办到。
如果起点或者终点有一个不能通行(为#),则看成无法办到。
注意:A、B不一定是两个不同的点。
输入格式
第1行是测试数据的组数 k,后面跟着 k 组输入。
每组测试数据的第1行是一个正整数 n,表示迷宫的规模是 n∗n 的。
接下来是一个 n∗n 的矩阵,矩阵中的元素为.或者#。
再接下来一行是 4 个整数 ha,la,hb,lb,描述 A 处在第 ha 行, 第 la 列,B 处在第 hb 行, 第 lb 列。
注意到 ha,la,hb,lb全部是从 0 开始计数的。
输出格式
k行,每行输出对应一个输入。
能办到则输出“YES”,否则输出“NO”。
数据范围
1≤n≤100
输入样例:
2
3
.##
..#
#..
0 0 2 2
5
.....
###.#
..#..
###..
...#.
0 0 4 0
1
2
3
4
5
6
7
8
9
10
11
12
13
输出样例:
YES
NO
- #include<stdio.h>
- #include<string.h>
- int k,n,e,b,c,d,ans;
- char a[105][105]={0};
- int st[105][105]={0};
- int p[4]={-1,0,1,0},q[4]={0,1,0,-1};//偏移矩阵
- void dfs(int x1,int y1,int x2,int y2)
- {
- if(x1==x2&&y1==y2)//出口
- {
- ans=1;
- return;
- }
- for(int i=0;i<4;i++)//递归
- {
- int j=x1+p[i];
- int k=y1+q[i];
- if(j>=0&&j<n&&k>=0&&k<n&&a[j][k]=='.'&&st[j][k]==0)//这里其实就是所谓的剪枝,即事先判断去除不必要的路径
- {
- st[j][k]=1;//标记不能回头
- dfs(j,k,x2,y2);
- }
- }
-
- }
-
- int main()
- {
- scanf("%d",&k);
- for(int i=0;i<k;i++)
- {
- ans=0;
- memset(a,0,sizeof(a));//重置矩阵
- scanf("%d",&n);
- for(int j=0;j<n;j++)
- {
- scanf("%s",a[j]);
- }
- scanf("%d%d%d%d",&e,&b,&c,&d);
- if(a[e][b]=='#'||a[c][d]=='#')
- {
- printf("NO");
- }
- else
- {
- st[e][b]=1;
- dfs(e,b,c,d);
- if(ans==1)
- {
- printf("YES\n");
- }
- else
- {
- printf("NO\n");
- }
- }
-
- }
-
- }
注意:这个里面提到的偏移矩阵,实际上两两组合起来就是确定从当前位置下一步的走法(上下左右、东南西北),根据实际情况会有所变化,也有的人习惯直接用二维矩阵来表示,原理是一样的
A:特点:
a:往往是求最优路径/方法;
b:基础的就是求方案数量。
B:例题
典型例题1——方案数量类型:
题目描述
马在中国象棋以日字形规则移动。
请编写一段程序,给定 n∗m 大小的棋盘,以及马的初始位置 (x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。
输入格式
第一行为整数 T,表示测试数据组数。
每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标 n,m,x,y。
输出格式
每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,若无法遍历棋盘上的所有点则输出 0。
数据范围
1≤T≤9,
1≤m,n≤9,
0≤x≤n−1,
0≤y≤m−1
输入样例:
1
5 4 0 0
1
2
输出样例:
32
- #include<stdio.h>
- #include<string.h>
- int T,n,m,index;
- char a[10][10];
- int p[8]={-2,-1,2,1,-2,-1,2,1},q[8]={1,2,1,2,-1,-2,-1,-2};//偏移量
- void dfs(int x,int y,int sum)
- {
- if(sum==n*m)
- {
- index++;
- return;
- }
- a[x][y]='F';
- for(int i=0;i<8;i++)
- {
- int j=x+p[i];
- int k=y+q[i];
- if(j>=0&&j<m&&k>=0&&k<n&&a[j][k]=='T')//剪枝
- {
- //printf("%d %d %d\n",j,k,sum);//测试语句,忽略
- dfs(j,k,sum+1);
- }
- }
- a[x][y]='T'; //回溯
- }
- int main()
- {
- scanf("%d",&T);
- for(int i=0;i<T;i++)
- {
- index=0;
- int x,y;
- scanf("%d%d%d%d",&n,&m,&x,&y);
- memset(a,'T',sizeof(a));
- dfs(x,y,1);//起始时占有一个点位
- printf("%d\n",index);
- }
- }
结合这些代码来总结一下回溯和剪枝就十分好理解了:
1:回溯用人话来说,就是在标记之后,又要消除标记,消除标记的递归过程,就是回溯。
2:剪枝用人话来讲,就是根据题目中或者隐藏的限制条件,来避免不必要的搜索,电脑性能好的甚至可以不用,但是做题由时间限制的话必须得考虑。
以上就是入门dfs的入门课,吃透了之后就基本能理解了,不然有些题连代码都看不懂,下面进行更复杂的应用
- #include <stdio.h>
- int a[10][10];//存值
- int b[10][10];//标记数组
- int min;
- int m,n,sum=0,index=0;
- int dx[4]={0,1,0,-1};//偏移矩阵
- int dy[4]={1,0,-1,0};
- void f(int s,int i,int j,int bs)
- {
- if(s==sum/2&&min>bs)
- min=bs;//判断是否更新答案
- b[i][j]=0; //此时a[0][0] 只作连接用 所以格子和值不增加
- for(int k=0;k<4;k++)
- {
- int x=dx[k]+i;
- int y=dy[k]+j;//边界判断和剪枝
- if(b[x][y]==1&&x>=0&&x<n&&x>=0&&x<m){
- if(x==0&&y==0) f(s,x,y,bs);
- else f(s+a[x][y],x,y,bs+1);
- }
- }
- b[i][j]=1;//消除标记
- }
- int main()
- {
- int i,j;
- scanf("%d%d",&m,&n);//输入宽、高
- min=n*m;
- for(i=0;i<n;i++)//注意坑点 先输入纵坐标的m 再输入的横坐标
- for(j=0;j<m;j++)
- {scanf("%d",&a[i][j]);//值
- sum+=a[i][j]; b[i][j]=1;//初始化标记数组
- }
- if(sum%2==1)printf("0");//预判是否可以分两部分
- else{ f(a[0][0],0,0,1);
- if(index!=0) printf("%d\n",min);
- else printf("0\n");
- }
- return 0;
- }
- #include<stdio.h>
- int a[1001][1001]={0};//邻接矩阵
- int c[1001]={0};//为0可以访问
- int b[1001]={0};//站点访问 次数
- int n,m,index=0;// 记录可行路径条数
-
- void dfs(int x,int y)
- {
- if(x==y)
- {
- index++;//得到可行路径的条数
- for(int i=1;i<=n;i++)//记录可行路径访问点的次数
- {
- if(c[i]==1)
- {
- b[i]++;
- }
- }
- }
- c[x]=1;
- for(int i=1;i<=n;i++)
- {
- if(a[x][i]==1&&c[i]==0)
- {
- dfs(i,y);
-
- }
- }
- c[x]=0;
- }
-
- int main()
- {
- int p,q,flog=0;
- scanf("%d%d",&n,&m);
- for(int i=1;i<=m;i++)//得到目标邻接矩阵
- {
- int x,y;
- scanf("%d%d",&x,&y);
- a[x][y]=1;
- a[y][x]=1;
- }
- scanf("%d%d",&p,&q);
- dfs(p,q);
- if(index==0)
- {
- printf("-1\n");
- }
- else
- {
- for(int i=1;i<=n;i++)
- {
- if(b[i]==index)//可行路径的条数和访问的次数相等,则表明该点位必需点
- {
- flog++;
- }
- }
- printf("%d\n",flog-1);
- }
- }
下面小结一下dfs基本应用模板:
- #include<>
- 设置原矩阵;
- 设置标记矩阵,....;
- 设置偏移矩阵;
- 设置一些全局变量;
- dfs()
- {
- if()//终止条件
- {
- 终止时会进行到的操作
- }
- 标记
- for()
- {
- 移动到下一步的操作;
- if()//剪枝,根据限制条件设置
- {
- dfs();
- }
- }
- 消除标记//回溯
- }
-
-
个人见解,希望有所帮助,不喜勿喷
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。