深度优先搜索是搜索的手段之一。它从某个状态开始,不断地转移状态,直到无法转移,然后回退到前一步的状态,继续转移到其他状态,如此重复,直到找到最终的解。
做这类题目,抓住两样东西:1.总体上递归几次(几层)?每一次递归确定一层上的数。 2.每次递归,有几种选择的情况。所以dfs()函数,只有两部分(if、else结构):1.(if部分)若每一层都选择了,判断是否符合条件,做出题目要求的操作。2.(else部分)若还有层没有选择,就做出选择,所有选择的情况列出。
下面是几个考察dfs的题目:
1.部分和问题(入门题)——南阳1282(dfs)
问题描述:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=1282
题目要求:在n个数中,选出某些,使得它们之和等于k,可以做到,输出YES,不可以做到,输出NO。
分析:有n个数,大体递归n(或n+1)次,分别确定n(或n+1)层的数;每次递归,有两种选择:加这个数、不加这个数。
其状态转移过程如下:
代码<c语言>:
1 //每个数分取和不取两种情况 2 #include<stdio.h> 3 int n,k,arr[21];//设成全局变量,是的dfs函数的参数减少,函数变简洁//dfs函数,用来确定第i层上的数 4 bool dfs(int i,int sum) 5 { 6 if(i==n)return sum==k; 7 //两种选择 8 if(dfs(i+1,sum)) return true;//不取这个数,sum+arr[i]表示到第i+1层数的和 9 if(dfs(i+1,sum+arr[i])) return true;//取这个数 10 return false; 11 }//主函数 12 int main() 13 { 14 int i; 15 while(~scanf("%d %d",&n,&k)) 16 { 17 for(i=0; i<n; i++) 18 { 19 scanf("%d",&arr[i]); 20 } 21 22 if(dfs(0,0))//第一层,是0,不是第一个数,所以要n+1层 23 printf("YES\n"); 24 else 25 printf("NO\n"); 26 } 27 return 0; 28 }
2.部分和问题——南阳1058(dfs+1组记录)
题目描述:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=1058
题目要求:在题目1的基础上,输出由那些数组成。
分析:同题目1,但是不仅要判断是否可以找出某些数之和正好是k,还要输出这些数,我们在递归的过程中,如果满足条件,就把数存入数组。本题的dfs函数,只比上一题多一行代码:result[j++]=arr[i],存数的过程。
注意:若给出的数,有多组都符合条件,此代码只输出一组。并且,选择的次序不同,会导致结果不同。如:
输入:4 12
4 8 1 7
“先加这个数”:输出:YES
4 1 7
“后加这个数”:输出:YES
4 8
但是,两种写法,虽然结果不同,都AC了。
代码<c语言>:
1 #include<stdio.h> 2 int n,k,j=0,arr[21],result[21];//arr[]存储题目给出的数,result[]存储符合条件的数的下标//dfs函数 3 bool dfs(int i,int sum){ 4 if(i==n)return sum==k; 5 if(sum>k)return false; 6 //下面自然是i<n的情况,有两种情况 7 /*if(dfs(i+1,sum+arr[i])) {//位置不同(先加这个数),结果不同,但是都AC了 8 result[j++]=arr[i];//记录取出的数据 9 return true; 10 }*/ 11 if(dfs(i+1,sum)) 12 return true; 13 if(dfs(i+1,sum+arr[i])) {//位置不同(后加这个数) 14 result[j++]=arr[i];//记录取出的数据,arr[]下标大的放在前面,输出的时候注意从后往前输出 15 return true; 16 } 17 return false; 18 }//主函数 19 int main(){ 20 int i; 21 while(~scanf("%d %d",&n,&k)){ 22 j=0;//初始化 23 for(i=0;i<n;i++){ 24 scanf("%d",&arr[i]); 25 } 26 27 if(dfs(0,0)){ 28 printf("YES\n"); 29 j=j-1; 30 while(j>=0){//输出 31 printf("%d ",result[j--]); 32 } 33 printf("\n"); 34 } 35 else 36 printf("NO\n"); 37 } 38 return 0; 39 }
**拓展:那怎样写,才能将所有的结果都输出呢?大部分题目还是要求这样的。(dfs+多组记录+还原)
思路:与前面的大同小异,异在:输入数据的时候,从下标1开始存,参数sum也定义为全局变量;及时还原,并且找到了就输出,不在主函数里来输出。(if、else结构)
输入:4 12
4 8 1 7
输出:YES
4 1 7
4 8
代码<c语言>:
#include<stdio.h> int n,k,arr[21],result[21],sum=0,flag=0; void dfs(int i)//(if、else部分) { int j; if(i==n+1) //已经全部做好选择(每一层都选择好了) { if(sum==k) //正好与所要求的相等 { ++flag; if(flag==1)//改组数据第一次找到符合的,输出YES,再次找到就不用输YES啦 printf("YES\n"); for(j=1; j<=n; j++)//找到就输出 { if(result[j]) printf("%d ",arr[j]); } printf("\n"); } else return; } else { //对下一个数进行选择 //1.取数 dfs(i+1);//2.不取数 sum+=arr[i]; result[i]=1;//表示用过 dfs(i+1);//对下一个数进行选择 result[i]=0;//表示没用过,还原 sum=sum-arr[i]; } return; } int main() { int i; //输入 while(~scanf("%d %d",&n,&k)) { flag=0;//初始化 sum=0; for(i=1; i<=n; i++) //从数组下标1开始存 { scanf("%d",&arr[i]); } dfs(1);//第一层,第一个数,所以要n层 if(flag==0) printf("NO\n"); } return 0; }
3.组合数——南阳32(dfs+记录+还原)
题目描述:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=32
题目要求:找出从自然数1、2、... 、n(0<n<10)中任取r(0<r<=n)个数的所有组合。还要求输出时,每一个组合中的值从大到小排列,组合之间按逆字典序排列。
分析:先不看输出要求,要确定r个数,则有r层,递归r次;那每一层有哪些选择呢?以第count(1<count<r)层为例,也就是要确定第count个数,这时第count-1个数已经确定了,第count个数一定在它之后,这样取出来的数就有一定的顺序,那第count个数能取1~n的最后一个数吗?自然是不可以的,不然剩下的r-count个数放哪呢?所以第count个数的下标要小于n-(r-count),所以每一层的选择是:“前一个数的下标”<i<n-r+count。每一层的数的下标可以用result[]保存。
考虑输出要求,逆字典序,所以输入时,倒着存,以n,n-1,n-2.......2,1存入数组,并且以下标1开始(方便)。
代码<c语言>:
#include <stdio.h> int arr[10],result[10],len,num;//result存取出来的数的下标,len——数组的长度,num——要取出数是个数 void dfs(int count){//(if、else结构) int i; if(count==num+1) //num个数都取出来了 { for(int j=1; j<=num; j++) { printf("%d",arr[result[j]]); } printf("\n"); } else{ for(i=result[count-1]+1; i<len-num+count; i++) //可选择的范围,其下标一定会在所取前一个数下标之后,在len-num+count之前(包括)它 { result[count]=i; dfs(count+1); } } return; } int main() { int n,r,i; scanf("%d %d",&n,&r); len=n+1; num=r; for(i=1; i<=n; i++) //数组长度为n+1,注意 { arr[i]=n-i+1;//倒着存 } dfs(1); return 0; }
4.四色问题——code<vs>1116(dfs+还原)
题目描述:http://www.codevs.cn/problem/1116/
题目要求:给地图上的点用4种颜色涂色,求共有多少种涂法,要求相邻点的颜色不同。
分析:n个点,n层,n次递归;每一个点颜色有4种选择,但是要去掉相邻点的颜色。
代码<c语言>:
#include<stdio.h> int a[10][10],b[10],c=0,n;//a[10][10]储存相邻关系,b[10]保存涂的颜色 void dfs(int x) //(if、else结构)x可以表示第几层,也可以表示第几个点 { int i,j;//if if(x==n+1) //表示所有点都涂完了 { c++; return;//这里return了,下面就不用写else。 }//else for(i=1; i<=4; i++) //涂四种颜色,四种选择 1,2,3,4表示四种颜色 { // b[x]=i;//涂色,放在这里不合适,抹去颜色只有两种途径,1.颜色可以这样涂,便可以dfs下去,等待回溯后,进行b[x]=0操作。2.颜色选错了,break后等待下一次选色,覆盖它的颜色。但问题在于若当前已经是1,2,3,4中最后一种颜色了,就覆盖不了了 //应该先判断能不能涂,再进行操作 for(j=1; j<=n; j++) { if(a[x][j]&&b[j]==i)//找出与当前 点 相邻,并且颜色相同的,就说明当前颜色涂错了 break; } if(j==n+1) //正常跳出,不是break出来的,就说明可以涂这种颜色 { b[x]=i;//涂色 dfs(x+1); b[x]=0;//涂完过后,还原 } } return; } int main() { //输入 int i,j; //freopen("1.txt","r",stdin); scanf("%d",&n); for(i=1; i<=n; i++) { for(j=1; j<=n; j++) { scanf("%d",&a[i][j]); } } dfs(1); printf("%d",c); return 0; }
5.素数环——南阳488(dfs+记录+还原)
题目描述:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=488
题目要求:若形成素数环,从1开始(其实第一个位置上的数已经确定),从大到小输出
分析:n个数,n层,可以代表素数环的n个位置;每个位置,有1~n个选择,只是,前面位置用过的,不能再用,这个数与前一位置上的数之和为不为素数,不能用。
代码<c语言>:
//超时问题 1.将相邻位之和是否为素数 的判断放在 取数的阶段,而不是先将数放好,再判断素数问题2.若n为奇数,一定不成素数环 3.判断素数,用埃氏筛选。 //特殊情况? n=1,1为奇数,但是可以成素数环 #include<stdio.h> #include<math.h> int n,a[21],result[21],A[50],flag2;//result数组从下标1开始存数 //埃氏筛选出素数,i为素数,A[i]=0;i不为素数,A[i]=1 void AS() { int q,i; q=sqrt(50); A[0]=1; A[1]=1;//A[i]=1表示i不为素数 for(i=0; i<=q; i++) { if(!A[i])//如果i为素数,则i的倍数不为素数 for(int j=i+i; j<50; j=i+j) //j为i的倍数 { A[j]=1; } } } void dfs(int count) { int i,j,flag=1;//flag用来标记相邻位是否是素数,flag2用来标记是否形成过素数环 if(count==n+1) //已经形成一个环 { //判断是否是素数环 /* for(j=1;j<n;j++){ if(A[result[j]+result[j+1]])//相邻位置之和不是素数。将素数的判断放在 每个位置上的数都选好之后,花时间长 flag=0; }*/ if(A[result[n]+result[1]])//头尾 flag=0; if(flag) //是素数环 { flag2=1;//表示形成过素数环 for(j=1; j<=n; j++) { printf("%d ",result[j]); } printf("\n"); } } else { for(i=1; i<=n; i++) //每一个位置(每一层)有n种选择 { if(a[i]&&!A[i+result[count-1]]) //之前没有用过 并且 相邻位置数之和为素数 这样的数才符合要求(将素数判断放在 选择数之前,节约时间) { result[count]=i; a[i]=0;//i用过后就用0标记 dfs(count+1); a[i]=i;//还原 } } } return; } int main() { int k=1; //输入 AS(); while(~scanf("%d",&n)&&n) { flag2=0;//初始化 printf("Case %d:\n",k++); if(n==1) printf("1\n"); else if(n%2) printf("No Answer\n"); else { for(int i=1; i<=n; i++) { a[i]=i;//将数放入数组 } result[1]=1;//已经确定 a[1]=0;//用过了 dfs(2);//确定第一个数 if(!flag2)//说明一组都没找着 printf("No Answer\n"); } } return 0; }
6.Lake Counting——poj488(dfs)
题目描述:http://poj.org/problem?id=2386
要求:输出水洼的个数
分析:其实就是简单的深度搜索(8种选择),搜索不下去的时候,一个水洼就确定了,又开始下一个搜索。‘W‘代表水,只有‘W’能走,走过的地方变为‘.’,避免重复走过,不必还原。
代码<c语言>:
#include<stdio.h> int m,n; char a[105][105]; void dfs(int x,int y) { int nx,ny,dx,dy; a[x][y]=‘.‘;//将遍历过的点,由‘W’变为‘.‘,避免再次遍历 for(dx=-1; dx<=1; dx++)//八个方向,8种选择 { for(dy=-1; dy<=1; dy++) { nx=x+dx;//八个方向遍历,不能写成nx,ny ny=y+dy; if(a[nx][ny]==‘W‘&&nx>=0&&nx<m&&ny>=0&&ny<n) //若下一个方向上的点 是水洼,且没超过边界,继续遍历 { dfs(nx,ny); } } } return; } int main() { int i,j,c=0; //输入 //freopen("1.txt","r",stdin); scanf("%d %d",&m,&n); getchar();//注意 for(i=0; i<m; i++) { for(j=0; j<n; j++) { scanf("%c",&a[i][j]); } getchar();//注意 } for(i=0; i<m; i++) { for(j=0; j<n; j++) { if(a[i][j]==‘W‘) //只遍历‘W‘ { dfs(i,j);//遍历结束一次,构成一个水洼 c++; } } } printf("%d",c); return 0; }
原文地址:https://www.cnblogs.com/li-yaoyao/p/9306824.html