回忆dfs

                                      回忆dfs

 在上次听了搜索之后,我对搜索的印象也是更加的深刻了(也许吧)。。那么我就在这里重新回忆一下搜索,以来巩固(也许吧

  搜索呢,大致分为两种(也许是就分为两种),一种是dfs,一种是bfs。Dfs呢,十分稳定,暴力超时九个点!但是代码书写容易;bfs呢,效率是dfs的n倍,但是代码书写的难度是和效率成正比的!!所以说我现在也只能大致掌握dfs,而bfs却只能算入门。。

接下来我会给大家dfs。

Dfs:

dfs是搜索中较为重要的一种,其模板形式有很多:如走迷宫、八皇后、质数肋骨等多种使用dfs’的方法。可以说,dfs的作用域是十分广泛的,但也是十分超时的。

 (接下来的基础内容来自学校教材)

dfs学习要经历三个阶段:1、递归  2、回溯(当然有些题不需要)  3、dfs

接下来三个内容逐个进行分析:

递归:一种程序结构,更多的考虑怎么构建递归式子和递归结构的实现,一般参数不会很多。

 回溯:比递归高级,但是更多的考虑把修改过的值在用过之后重新改回来。需要更过的考虑递归的参数与局部变量、全局变量之间的关系。

 dfs:需要掌握基本的dfs结构,需要更多地考虑搜索的顺序,如果优化。

那么搜索大致结构如下:

框架【一】

            Int dfs(int k){

              For (int i=1;i<=算符种数;i++)

                 IF (满足条件){

                      保存结果;

                      If (到达目的地) 输出解;

                      Else dfs(k+1);

                  恢复:保存结果之前的状态{回溯}

}

}

    

 当然框架是死的,但题确实活的,所以我们还得依题而论。

  框架【二】

              Int dfs(int k){

                If (到达目的地) 输出解;

                Else

                  For (int i=1;i<=算符种数;i++)

                     IF (满足条件){

                         保存结果;

                         dfs(k+1);

                         恢复:保存结果之前的状态{回溯}

}

}

 

按照模板而言,我是比较喜欢框架二的,当然这也是按个人喜好定。

好,那么对于刚接触搜索的人而言。搜索一般就是“只有思路,没有代码”。我刚接触时当然也是这样,看着搜索一脸蒙逼。所以搜索想要学好也只能靠刷题,从之吸取经验,从而提高能力。那么接下来就放几道题目。

1、           迷宫

题目描述:有一个N*M的方格迷宫。其中有T个障碍,障碍不能通过。给定起始坐标和终点坐标,问一共有几种走法?(每次只能上下左右走)

 

  题目分析:想做这道题目,不用搜索还是十分难的,因为当前状态是很难记录的,就算暴力也很难(虽然dfs就是暴力)。那么我们做这道题是就得有一个思路导图:

                             1、   到达,方案数累加一。

                            

判断是否到达目的地         

                             2、未到达,继续搜索四个方向,直至到达。

那么做这道题就得涉及到一个小知识点:坐标增量了。坐标增量我们一般用数组dx和dy表示,当然他们数组的上限就是方向种数,例如这道题就是4。

那么方向数组有什么用呢??当然就是记录方向的啦。dx和dy的每一个相同下标的存值就表示一个方向。

那么这听着自然是很蒙蔽的,接下来给图解释。


int
dx[5]={-1,1,0,0};//自坐向右为上、下、右、左

int dy[5]={0,0,1,-1};//自坐向右为上、下、右、左

 

那么具体图例解释:

x-1,y(此坐标点为当前坐标点的上方,也就是题目中所说的上方向)(2,3)

此时x和y就同时加上了dx[1]和dy[1].

x,y-1(此坐标点为当前坐标点的左方,也就是题目中说的左方向)

(3,2)

此时x和y就同时加上了dx[4]和dy[4]

              

        x ,y(当前坐标)

               

           (3,3)

x,y+1(此坐标点为当前坐标点的右方,也就是题目中说的右方向)

(3,4)

此时x和y就同时加上了dx[3]和dy[3]

x+1,y(此坐标点为当前坐标点的下方,也就是题目中所说的下方向)(4,3)

此时x和y就同时加上了dx[2]和dy[2]

那么坐标增量就是根据横纵坐标系去理解,也是比较好懂的。

好,那么既然思路有了,坐标增量也知道了,具体代码如下:

#include<bits/stdc++.h>

using namespacestd;

intn,m,t,t1,h1,t2,h2,ans=0;

intdx[5]={-1,1,0,0};//

intdy[5]={0,0,1,-1};//如上,伟大的坐标增量

boola[501][501]={};//判断当前点有没有障碍,同时也避免了出界的可能

void dfs(intx,int y){

      if (x==t2&&y==h2){//如果到达目的地

              ans++;return;//方案数累加一。跳出循环

       }

       a[x][y]=0;//因为当前点已经走过,定为不可走

      for (int i=0;i<=3;i++){//四个方向搜索

              int xx=x+dx[i],yy=y+dy[i];//当前走过方向之后的下标

             if (a[xx][yy]) dfs(xx,yy);//如果没障碍或没越界或没走过

       }

       a[x][y]=1;//回溯一步

}

int main(){

       cin>>n>>m>>t;

      for (int i=1;i<=n;i++)

        for(int j=1;j<=m;j++)

          a[i][j]=1;//现将迷宫范围内赋初值为可走

       cin>>t1>>h1>>t2>>h2;//起点与终点

      for (int i=1;i<=t;i++){//障碍的输入

              int x,y;

             scanf("%d%d",&x,&y);

              a[x][y]=0;//定为不可走

       }

      dfs(t1,h1);//从起点搜索

       cout<<ans;//输出

      return 0;

}

 

这题只是一个搜索的基本模板,没什么难度,得好好理解。

2、           细胞问题

题目简介:一矩形阵列由数字09组成,数字19代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。如阵列:

0234500067

1034560500

2045600671

0000000089

4个细胞。

题目分析:那么这四个细胞还可能有点难找,具体如下:

0234500067

1034560500

2045600671

0000000089

每种颜色对应一种细胞

那么将样例解释一遍后题目就变得蛮清晰的了。还是原来的方法,从图中的某一个点开始,上下左右搜索,如果是数字,就继续搜索数字,再讲数字置零,视为已被搜索过。

 

那么我们就要思考一个问题:具体从哪个点开始呢?

 

很显然,是从第一个搜索到的非零数字开始。因为只要出现了一个非零数,就意味着至少有一个细胞,就以这个数字的位置为起点开始搜索,把和它有关的所有非零数(也就是细胞)置零。然后就在查找,找到一个非零数,继续搜索……

 

那么很显然这道题也需要用方向数组,那么方向数组上面已经解释过了,这里就不解释了。

 

这道题的输入用字符数组输入即可。

 

那么代码如下:

#include<bits/stdc++.h>
using namespace std;
char a[201][201];
int ans=0;
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};//。。方向数组
void dfs(int x,int y){
       int xx,yy;
       a[x][y]='0';//将当前细胞置零
       for (int i=0;i<=3;i++){//四个方向搜索
              xx=x+dx[i];yy=y+dy[i];//当前下标
              if (a[xx][yy]!='0') dfs(xx,yy);//如果有个细胞,继续遍历
       }
}
int main(){
       int n,m;
       memset(a,'0',sizeof(a));//现将布尔数组全定为‘0’,避免了出界
       scanf("%d%d",&n,&m);
       for (int i=1;i<=n;i++)
         for (int j=1;j<=m;j++)
           cin>>a[i][j];//输入
       for (int i=1;i<=n;i++)
         for (int j=1;j<=m;j++)
           if (a[i][j]!='0'){//判断,如果当前出现了细胞,就把和之有关的细胞都遍历一遍,在置零。
              ans++;//统计一个细胞
              dfs(i,j);//开始搜索
           }
           cout<<ans;//输出
           return 0;
}

 

 

3、           工作分配问题

 

题意简述:设有n件工作分配给n个人。将工作i分配给第j个人所需的费用为Cij。试设计一个算法,为每一个人都分配1件不同的工作,并使总费用达到最小。设计一个算法,对于给定的工作费用,计算最佳工作分配方案,使总费用达到最小。

 

输入样例:                                  输出样例:
3                                           9
10 2 3
2 3 4
3 4 5

 

方案数为第一项工作分配给第二个人;第二项工作分配给第一个人;第三项工作分配给第三个人。

 

题目分析:这道题的搜索形式就不同于前几题的了,前面几题都是以坐标增量的形式出现的,而这题却不同。这题主要的做法就是暴力,枚举每一项工作,在枚举每一项工作的每一个人,如果当前的人没有工作,那么就把工作分配给当前的人……当计算到最后一项工作时,就可以做个统计,取最小值。

 

那么具体代码如下:

#include<bits/stdc++.h>
using namespace std;
int n;
int a[21][21]={};//工作,表示第i想工作给第j个人的费用
int minn=1000000000;//找最小值
bool f[101]={};//判断当前人有没有工作
void dfs(int t,int ans){
       if (t>n){//如果工作都被选完了
              minn=min(ans,minn);//取较小值
              return;//退出这层
       }
       if (ans>minn)return;//一个小剪枝,如果总费用已经大于当前最消费用,就不用看,直接退出这层递归
       for (int i=1;i<=n;i++)//n个人循环
         if (!f[i]){//表示当前人没有工作
              f[i]=1;//标志为有工作
              ans+=a[t][i];//总值累加
              dfs(t+1,ans);//将下一个工作进行搜索
              ans-=a[t][i];//回溯
              f[i]=0;//回溯
         }
}
int main(){
       scanf("%d",&n);
       for (int i=1;i<=n;i++)
         for (int j=1;j<=n;j++)
           scanf("%d",&a[i][j]);//输入
       dfs(1,0);//从第一个工作,零元是开始搜索
       cout<<minn;//输出
       return 0;
}

 

 

 

4、           质数肋骨

题意简述:有一个长度为n的肋骨。从右边开始切下肋骨,每次还剩下的肋骨上的数字都组成一个质数,,这样的数称为质数肋骨。例如n=4,质数肋骨如下:2333

2339//例:2339为质数,233为质数,23位质数,2为质数。

2393

2399

2939

3119

3137

3733

3739

3793

3797

5939

7193

7331

7333

7393

问所有长度为n的所有质数肋骨有哪些?

 

 

题目分析:首先,可以想到一种暴力手法:先用筛法求素数讲长为n以内的所有素数求出;在枚举所有长为n的数,一层层剥离、判断……虽然会超时,但是也可以给大家开一开脑洞。

暴力代码如下:

#include<bits/stdc++.h>
using namespace std;
bool a[99999999]={};//最多8位
int n,x=1,y=10,s;
int main(){
       cin>>n;
       for (int i=1;i<=n-1;i++)//范围计算
         x*=10,y*=10;
       a[1]=a[0]=1;//1不是质数
       for (int i=2;i<=y-1;i++)
         if (!a[i])
           for (int j=2;j<=(y-1)/i;j++)
             a[i*j]=1;//线性筛素数过程
       for (int i=x;i<=y-1;i++){//在范围内循环判断
              bool f=1;//默认当前值为质数
              s=i;//将i存给s进行剥离判断。
              while (s>0){//一层层剥离
                    if (a[s]){f=0;break;}//只要不是质数就退出
                    s/=10;
              }
              if (f) cout<<i<<endl;//如果是个质数就输出
       }
       return 0;
}
贪心过样例!暴力出奇迹!


暴力方法打一通是不是很爽!!超时的是不是也很爽!!这个暴力算法只是一个开胃菜。接下来才是真正的分析:

  如果直接枚举所有的n位数,在判断,肯定会超时的。

  通过对问题的思考,我们发现:如果一个数要满足条件,最高位数字只能是2,3,5,7,因为是质数肋骨的基础的基础就是每一位(这不是指真的每一位,而是质数肋骨的条件)都得是质数。从第二位开始,我们填充的数字只能是奇数(偶数不用考虑)。然后没加一位数字,没判断一次,如果一旦不满足条件就退出。

 

本体就有了一点dfs的味道,更在乎搜索的策略与优化。

 

那么具体代码如下:

#include<bits/stdc++.h>
using namespace std;
int n;
bool f(int x){//判断数字是否为质数,是则返回真,否则返回假。
       if (x==1)return 0;//1不是质数
       if (x>1&&x<4)return 1;//在1到4范围内的2和3是质数
       for (int i=2;i<=sqrt(x);i++)//质数判断的范围,可以去记一记。
         if (x%i==0)return 0;//如果一旦能被一个数整除,那么就一定不是质数,返回假
       return 1;//如果成功活到了这儿,就说明之前的循环没有一个数能够整除当前数,说明当前数就是一个质数。
}
void dfs(int ans,int len){
       if (!f(ans)) return;//如果是不是一个质数的话就退出递归
       if (len==n) {//如果长度达到了n
         cout<<ans<<endl;//输出这个数,这里不用再次判断是否为质数,因为之前已经判断过了。
         return;//推出递归
       }
       for (int i=1;i<=9;i=i+2) dfs(ans*10+i,len+1);//同样,循环奇数还是凑 
}
int main(){
       cin>>n;//输入位数
       for (int i=1;i<=9;i++)//判断个位
         if(f(i)) dfs(i,1);//如果各位是个质数,就说明有可能组成质数肋骨,就进行搜索。
       return 0;
}

 

好,这就是较为经典的四题搜索(虽然其他经典的题目还有一大堆。。)。

 

小总结:这就是dfs,他就是由上面的基础的两个模板变化而来。但是学习dfs就不能太过死板,被那两个框架框住,因为有很多题目都不是这种形式的,都是自己做、自已创造。所以,学习dfs需要懂得灵活运用,举一反三(虽然我还没够到达那步。。)。不过最重要的还是多刷题!多刷题!多刷题!刷题是十分重要的。。

猜你喜欢

转载自blog.csdn.net/huang_ke_hai/article/details/79066214
dfs