深度搜索入门

Posted li-yaoyao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度搜索入门相关的知识,希望对你有一定的参考价值。

       深度优先搜索是搜索的手段之一。它从某个状态开始,不断地转移状态,直到无法转移,然后回退到前一步的状态,继续转移到其他状态,如此重复,直到找到最终的解。

做这类题目,抓住两样东西: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 "); 24 else 25 printf("NO "); 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 "); 29 j=j-1; 30 while(j>=0){//输出 31 printf("%d ",result[j--]); 32 } 33 printf(" "); 34 } 35 else 36 printf("NO "); 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
");
            for(j=1; j<=n; j++)//找到就输出
            {
                if(result[j])
                    printf("%d ",arr[j]);
            }
            printf("
");
        }
        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 "); } 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("
");
    }
    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("
");
        }
    }
    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:
",k++);
        if(n==1)
            printf("1
");
        else if(n%2)
            printf("No Answer
");
        else
        {
            for(int i=1; i<=n; i++)
            {
                a[i]=i;//将数放入数组
            }
            result[1]=1;//已经确定
            a[1]=0;//用过了
            dfs(2);//确定第一个数
            if(!flag2)//说明一组都没找着
                printf("No Answer
");
        }
    }
    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;
}

 









以上是关于深度搜索入门的主要内容,如果未能解决你的问题,请参考以下文章

算法入门深度优先搜索(DFS)

⭐算法入门⭐《深度优先搜索》简单02 —— LeetCode 617. 合并二叉树

深度搜索入门

人工智障入门实战1使用深度优先搜索实现 Amazing-Brick 小游戏的自动控制

算法入门深度优先搜索(简单 - 第二题)LeetCode 617

算法入门深度优先搜索(中等 - 第二题)LeetCode 46