回忆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就是暴力)。那么我们就得想到dfs

那么做这道题就得涉及到一个小知识点:坐标增量了。坐标增量我们一般用数组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));
	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件不同的工作,并使总费用达到最小。设计一个算法,对于给定的工作费用,计算最佳工作分配方案,使总费用达到最小。

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

 

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

 

那么具体代码如下:

 

#include<bits/stdc++.h>
using namespace std;
int n,a[21][21]={},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++)
	  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]={};
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;
	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;
		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;
	if (x>1&&x<4)return 1;
	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) {
	  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需要懂得灵活运用,举一反三(虽然我还没够到达那步。。)。不过最重要的还是多刷题!多刷题!多刷题!刷题是十分重要的。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值