当前位置:   article > 正文

算法dfs+剪枝(见代码中)+回溯详解与例题提升_dfs剪枝例题

dfs剪枝例题

前言:深入学习了两天dfs算法相关的知识,根据自己的学习认识总结一下:

常见的dfs算法题有如下几类:

1:不需要回溯的dfs

      A:特点:

               a:判断 “是否存在” 的问题,遍历所有的点直接判断;

               b:要求能到达的 “数量” 问题;

     

       B:例题

          典型的例题1——数量问题:红与黑_牛客题霸_牛客网 (nowcoder.com)

 

  1. #include<stdio.h>
  2. int m,n,i,j;
  3. char a[30][30];
  4. int dfs(int x,int y)
  5. {
  6. if(x>=m||x<0||y>=n||y<0)//边界
  7. {
  8. return 0;
  9. }
  10. else if(a[x][y]=='#')//限制条件
  11. {
  12. return 0;
  13. }
  14. else//正常搜索条件
  15. {
  16. a[x][y]='#';//标记不能回头
  17. return 1+dfs(x-1,y)+dfs(x+1,y)+dfs(x,y-1)+dfs(x,y+1);//递归公式
  18. }
  19. }
  20. int main()
  21. {
  22. while(scanf("%d%d",&m,&n)!=EOF)
  23. {
  24. for(i=0;i<m;i++)
  25. {
  26. scanf("%s",a[i]);
  27. }
  28. for(i=0;i<m;i++)
  29. {
  30. for(j=0;j<n;j++)
  31. {
  32. if(a[i][j]=='@')
  33. {
  34. printf("%d\n",dfs(i,j));
  35. break;
  36. }
  37. }
  38. }
  39. }
  40. }

          例题2——是否存在的问题:

原题目描述
一天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
 

  1. #include<stdio.h>
  2. #include<string.h>
  3. int k,n,e,b,c,d,ans;
  4. char a[105][105]={0};
  5. int st[105][105]={0};
  6. int p[4]={-1,0,1,0},q[4]={0,1,0,-1};//偏移矩阵
  7. void dfs(int x1,int y1,int x2,int y2)
  8. {
  9. if(x1==x2&&y1==y2)//出口
  10. {
  11. ans=1;
  12. return;
  13. }
  14. for(int i=0;i<4;i++)//递归
  15. {
  16. int j=x1+p[i];
  17. int k=y1+q[i];
  18. if(j>=0&&j<n&&k>=0&&k<n&&a[j][k]=='.'&&st[j][k]==0)//这里其实就是所谓的剪枝,即事先判断去除不必要的路径
  19. {
  20. st[j][k]=1;//标记不能回头
  21. dfs(j,k,x2,y2);
  22. }
  23. }
  24. }
  25. int main()
  26. {
  27. scanf("%d",&k);
  28. for(int i=0;i<k;i++)
  29. {
  30. ans=0;
  31. memset(a,0,sizeof(a));//重置矩阵
  32. scanf("%d",&n);
  33. for(int j=0;j<n;j++)
  34. {
  35. scanf("%s",a[j]);
  36. }
  37. scanf("%d%d%d%d",&e,&b,&c,&d);
  38. if(a[e][b]=='#'||a[c][d]=='#')
  39. {
  40. printf("NO");
  41. }
  42. else
  43. {
  44. st[e][b]=1;
  45. dfs(e,b,c,d);
  46. if(ans==1)
  47. {
  48. printf("YES\n");
  49. }
  50. else
  51. {
  52. printf("NO\n");
  53. }
  54. }
  55. }
  56. }

注意:这个里面提到的偏移矩阵,实际上两两组合起来就是确定从当前位置下一步的走法(上下左右、东南西北),根据实际情况会有所变化,也有的人习惯直接用二维矩阵来表示,原理是一样的

2:需要回溯的dfs:

    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
 

  1. #include<stdio.h>
  2. #include<string.h>
  3. int T,n,m,index;
  4. char a[10][10];
  5. int p[8]={-2,-1,2,1,-2,-1,2,1},q[8]={1,2,1,2,-1,-2,-1,-2};//偏移量
  6. void dfs(int x,int y,int sum)
  7. {
  8. if(sum==n*m)
  9. {
  10. index++;
  11. return;
  12. }
  13. a[x][y]='F';
  14. for(int i=0;i<8;i++)
  15. {
  16. int j=x+p[i];
  17. int k=y+q[i];
  18. if(j>=0&&j<m&&k>=0&&k<n&&a[j][k]=='T')//剪枝
  19. {
  20. //printf("%d %d %d\n",j,k,sum);//测试语句,忽略
  21. dfs(j,k,sum+1);
  22. }
  23. }
  24. a[x][y]='T'; //回溯
  25. }
  26. int main()
  27. {
  28. scanf("%d",&T);
  29. for(int i=0;i<T;i++)
  30. {
  31. index=0;
  32. int x,y;
  33. scanf("%d%d%d%d",&n,&m,&x,&y);
  34. memset(a,'T',sizeof(a));
  35. dfs(x,y,1);//起始时占有一个点位
  36. printf("%d\n",index);
  37. }
  38. }

结合这些代码来总结一下回溯和剪枝就十分好理解了:

1:回溯用人话来说,就是在标记之后,又要消除标记,消除标记的递归过程,就是回溯。

2:剪枝用人话来讲,就是根据题目中或者隐藏的限制条件,来避免不必要的搜索,电脑性能好的甚至可以不用,但是做题由时间限制的话必须得考虑。

以上就是入门dfs的入门课,吃透了之后就基本能理解了,不然有些题连代码都看不懂,下面进行更复杂的应用

3:提升应用

例题1:蓝桥杯2013年第四届真题-剪格子 - C语言网

  1. #include <stdio.h>
  2. int a[10][10];//存值
  3. int b[10][10];//标记数组
  4. int min;
  5. int m,n,sum=0,index=0;
  6. int dx[4]={0,1,0,-1};//偏移矩阵
  7. int dy[4]={1,0,-1,0};
  8. void f(int s,int i,int j,int bs)
  9. {
  10. if(s==sum/2&&min>bs)
  11. min=bs;//判断是否更新答案
  12. b[i][j]=0; //此时a[0][0] 只作连接用 所以格子和值不增加
  13. for(int k=0;k<4;k++)
  14. {
  15. int x=dx[k]+i;
  16. int y=dy[k]+j;//边界判断和剪枝
  17. if(b[x][y]==1&&x>=0&&x<n&&x>=0&&x<m){
  18. if(x==0&&y==0) f(s,x,y,bs);
  19. else f(s+a[x][y],x,y,bs+1);
  20. }
  21. }
  22. b[i][j]=1;//消除标记
  23. }
  24. int main()
  25. {
  26. int i,j;
  27. scanf("%d%d",&m,&n);//输入宽、高
  28. min=n*m;
  29. for(i=0;i<n;i++)//注意坑点 先输入纵坐标的m 再输入的横坐标
  30. for(j=0;j<m;j++)
  31. {scanf("%d",&a[i][j]);//
  32. sum+=a[i][j]; b[i][j]=1;//初始化标记数组
  33. }
  34. if(sum%2==1)printf("0");//预判是否可以分两部分
  35. else{ f(a[0][0],0,0,1);
  36. if(index!=0) printf("%d\n",min);
  37. else printf("0\n");
  38. }
  39. return 0;
  40. }

   

    

例题二:蓝桥杯2013年第四届真题-危险系数 - C语言网

  1. #include<stdio.h>
  2. int a[1001][1001]={0};//邻接矩阵
  3. int c[1001]={0};//0可以访问
  4. int b[1001]={0};//站点访问 次数
  5. int n,m,index=0;// 记录可行路径条数
  6. void dfs(int x,int y)
  7. {
  8. if(x==y)
  9. {
  10. index++;//得到可行路径的条数
  11. for(int i=1;i<=n;i++)//记录可行路径访问点的次数
  12. {
  13. if(c[i]==1)
  14. {
  15. b[i]++;
  16. }
  17. }
  18. }
  19. c[x]=1;
  20. for(int i=1;i<=n;i++)
  21. {
  22. if(a[x][i]==1&&c[i]==0)
  23. {
  24. dfs(i,y);
  25. }
  26. }
  27. c[x]=0;
  28. }
  29. int main()
  30. {
  31. int p,q,flog=0;
  32. scanf("%d%d",&n,&m);
  33. for(int i=1;i<=m;i++)//得到目标邻接矩阵
  34. {
  35. int x,y;
  36. scanf("%d%d",&x,&y);
  37. a[x][y]=1;
  38. a[y][x]=1;
  39. }
  40. scanf("%d%d",&p,&q);
  41. dfs(p,q);
  42. if(index==0)
  43. {
  44. printf("-1\n");
  45. }
  46. else
  47. {
  48. for(int i=1;i<=n;i++)
  49. {
  50. if(b[i]==index)//可行路径的条数和访问的次数相等,则表明该点位必需点
  51. {
  52. flog++;
  53. }
  54. }
  55. printf("%d\n",flog-1);
  56. }
  57. }

       总结:

       在写dfs题的时候,往往是在矩阵的基础上进行的,有些矩阵里的值有实际作用的,就不能轻易的在原矩阵上做修改,而是需要通过标记矩阵来进行标记,根据实际情况,标记矩阵的形式和数量也会变化,至于偏移矩阵,根据移动的方式来看是否需要设置。(宏的工作)

         而为了减少dfs的复杂度,根据题意往往是由很明显的不符合项,可以在dfs之前就完成判断(主函数里的工作)

        dfs与剪枝和和回溯往往会被同时涉及到,如果不明白递归原理,则可以记一记这个回溯模板(dfs函数里的工作)

       同时,dfs参数的模式定义比较个性化,主要包含:起点、终点、随递归变化的终止元素、固定的参考值等。(dfs函数的设置)

下面小结一下dfs基本应用模板:

  1. #include<>
  2. 设置原矩阵;
  3. 设置标记矩阵,....;
  4. 设置偏移矩阵;
  5. 设置一些全局变量;
  6. dfs()
  7. {
  8. if()//终止条件
  9. {
  10. 终止时会进行到的操作
  11. }
  12. 标记
  13. for()
  14. {
  15. 移动到下一步的操作;
  16. if()//剪枝,根据限制条件设置
  17. {
  18. dfs();
  19. }
  20. }
  21. 消除标记//回溯
  22. }

    

个人见解,希望有所帮助,不喜勿喷

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/907317
推荐阅读
相关标签
  

闽ICP备14008679号