DFS

深度优先搜索

一、特点

从最开始的状态出发,遍历所有能到达的地方,每个状态只会进行一次,通过递归的思想实现

模板:摘自于https://blog.csdn.net/qq_40511966/article/details/86539631

void dfs()//参数用来表示状态 ,参数可以是一个数n//表示格数

              //参数也可以是两个数//r和c//n和level
    {  
        if(到达终点状态)  
        {  
            ...//根据题意添加  
            return;  
        }  
        if(越界或者是不合法状态)  
            return;  
        if(特殊状态)//剪枝
            return ;

        if(合并状态)

          //根据题意添加,注意也要初始化和标记//回溯
        for(扩展方式)  
        {  
            if(扩展方式所达到状态合法)  
            {  
                修改操作;//根据题意来添加  
                标记;  
                dfs();  
                (还原标记);  
                //是否还原标记根据题意  
                //如果加上(还原标记)就是 回溯法  
            } 
        }  
    } 

二、例题

例一          部分和问题:

给定整数 a1、a2、…、an,判断是否可以从中选出若干数,使它们的和恰好为 k

限制条件

 1 ≤ n ≤ 20
 108 ≤ ai ≤ 108
 108 ≤ k ≤ 108

输入

n=4
a={1,2,4,7}

k=13

输出

Yes

思路:运用递归,每个数有两种情况,分别是加上,不加上,然后一个一个来即可,复杂度2的n次方

代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#define maxn 30
using namespace std;
int n;//元素的个数
int sum;//部分和
int tmp[maxn];

int judge(int nn,int summ)//dfs到第几个数,现在的和是多少
{
    if(nn==n)
        return summ==sum;//注意循环到最后一个数,不一定返回0,需要判断它和部分和是否相等
    if(judge(nn+1,summ))
        return 1;
    if(judge(nn+1,summ+tmp[nn]))
        return 1;
    return 0;
}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%d",&tmp[i]);
    scanf("%d",&sum);
    if(judge(0,0))
        printf("Yes\n");
    else
        printf("No\n");
    return 0;
}

 


例题二:Lake Counting

有一个N*M的园子,雨后积水,八联通的积水被认为是连接在一起的。请求出园子里共有多少水洼?

N,M<=100

样例:

10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.

输出:

3

题解:遍历有w的点,并将其改为.,遍历其八个方向,如果符合的话,再次遍历。复杂度为,8*N*M

题解:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 110
using namespace std;
int x,y;//总行数和总列数
int ans=0;
char water[maxn][maxn];//水洼的字符串

void dfs(int tx,int ty)
{
   water[tx][ty]='.';//更新状态
   for(int i=-1;i<=1;i++)
   {
       for(int j=-1;j<=1;j++)
       {
           int xx=tx+i,yy=ty+j;
           if(xx>=0&&xx<x&&yy>=0&&yy<y)//符合要求
           {
               if(water[xx][yy]=='W')
                   dfs(xx,yy);
           }
       }
   }
}

int main()
{
    scanf("%d%d",&x,&y);
    for(int i=0;i<x;i++)
        scanf("%s",water[i]);
    for(int i=0;i<x;i++)
        for(int j=0;j<y;j++)
            if(water[i][j]=='W')
            {
                dfs(i,j);
                ans++;
            }
    printf("%d\n",ans);
    return 0;
}

 


例题三、Red and Black

 题目:

There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can't move on red tiles, he can move only on black tiles.

Write a program to count the number of black tiles which he can reach by repeating the moves described above.

Input

The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20.

There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.

'.' - a black tile
'#' - a red tile
'@' - a man on a black tile(appears exactly once in a data set)
The end of the input is indicated by a line consisting of two zeros.

Output

For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself).

Sample Input

    6 9
    ....#.
    .....#
    ......
    ......
    ......
    ......
    ......
    #@...#
    .#..#.
    11 9
    .#.........
    .#.#######.
    .#.#.....#.
    .#.#.###.#.
    .#.#..@#.#.
    .#.#####.#.
    .#.......#.
    .#########.
    ...........
    11 6
    ..#..#..#..
    ..#..#..#..
    ..#..#..###
    ..#..#..#@.
    ..#..#..#..
    ..#..#..#..
    7 7
    ..#.#..
    ..#.#..
    ###.###
    ...@...
    ###.###
    ..#.#..
    ..#.#..
    0 0

Sample Output

    45
    59

     6

    13

题意:一个人站在@黑色格子位置,该人只能走黑色格子(用‘.’表示),不能走红色格子(用#表示),只能上下左右移动,求出其所能走的最大黑色格子数。

题解:本题依然采用dfs进行遍历,并将每个符合条件的改为#,以保证只会遍历一次

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
int N,M,ans,a[]= {0,0,1,-1},b[]= {1,-1,0,0};//a和b是用来上下左右移动的
string s[60];
void dfs(int x,int y)
{
    ans++;
    s[x][y]='#';
    int dx,dy;
    for(int i=0; i<4; i++)
    {
        dx=x+a[i];
        dy=y+b[i];
        if(dx>=0&&dx<N&&dy>=0&&dy<M&&s[dx][dy]=='.')
            dfs(dx,dy);
    }
}
int main()
{
    int fl=1;
    while(cin>>M>>N&&M)
    {
        ans=0;
        for(int i=0; i<N; i++)
            cin>>s[i];
        for(int i=0; i<N; i++)
            for(int j=0; j<M; j++)
                if(s[i][j]=='@')
                {
                    fl=0;
                    dfs(i,j);
                    break;
                }
        cout<<ans<<endl;
    }
    return 0;
}
例题四、Oil Deposits

 

Problem Description

 

The GeoSurvComp geologic survey company is responsible for detecting underground oil deposits. GeoSurvComp works with one large rectangular region of land at a time, and creates a grid that divides the land into numerous square plots. It then analyzes each plot separately, using sensing equipment to determine whether or not the plot contains oil. A plot containing oil is called a pocket. If two pockets are adjacent, then they are part of the same oil deposit. Oil deposits can be quite large and may contain numerous pockets. Your job is to determine how many different oil deposits are contained in a grid.

 

Input

 

The input file contains one or more grids. Each grid begins with a line containing m and n, the number of rows and columns in the grid, separated by a single space. If m = 0 it signals the end of the input; otherwise 1 <= m <= 100 and 1 <= n <= 100. Following this are m lines of n characters each (not counting the end-of-line characters). Each character corresponds to one plot, and is either `*', representing the absence of oil, or `@', representing an oil pocket.

 

Output

 

For each grid, output the number of distinct oil deposits. Two different pockets are part of the same oil deposit if they are adjacent horizontally, vertically, or diagonally. An oil deposit will not contain more than 100 pockets.

 

Sample Input

 

1 1
*
3 5
*@*@*
**@**
*@*@*
1 8
@@****@*
5 5
****@
*@@*@
*@**@
@@@*@
@@**@
0 0

 

Sample Output

 

0 1 2 2
题解:和之前一样
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
string s[105];
int N,M,ans;
void dfs(int x,int y)
{
    s[x][y]='*';
    int dx,dy;
    for(int i=-1; i<=1; i++)
    {
        for(int j=-1; j<=1; j++)
        {
            dx=x+i;
            dy=y+j;
            if(dx>=0&&dx<N&&dy>=0&&dy<M&&s[dx][dy]=='@')
                dfs(dx,dy);
        }
    }
}
int main()
{
    while(cin>>N>>M&&M)
    {
        ans=0;
        for(int i=0; i<N; i++)
            cin>>s[i];
        for(int i=0; i<N; i++)
        {
            for(int j=0; j<M; j++)
                if(s[i][j]=='@')
                {
                    dfs(i,j);
                    ans++;
                }
        }
        cout<<ans<<endl;
    }
    return 0;
}
例题:
数独游戏:

    你一定听说过“数独”游戏。
    如下图所示,玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个同色九宫内的数字均含1-9,不重复。

    在这里插入图片描述

    数独的答案都是唯一的,所以,多个解也称为无解。
    本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。但对会使用计算机编程的你来说,恐怕易如反掌了。
    本题的要求就是输入数独题目,程序输出数独的唯一解。我们保证所有已知数据的格式都是合法的,并且题目有唯一的解。
    格式要求,输入9行,每行9个数字,0代表未知,其它数字为已知。
    输出9行,每行9个数字表示数独的解。
    输入:

    005300000
    800000020
    070010500
    400005300
    010070006
    003200080
    060500009
    004000030
    000009700

    程序应该输出:

    145327698
    839654127
    672918543
    496185372
    218473956
    753296481
    367542819
    984761235
    521839764

    再例如,输入:

    800000000
    003600000
    070090200
    050007000
    000045700
    000100030
    001000068
    008500010
    090000400

    程序应该输出:

    812753649
    943682175
    675491283
    154237896
    369845721
    287169534
    521974368
    438526917
    796318452
代码:
//第一个坑是输入的是字符串,不可能是int类型
//所以要从0开始
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
string s[10];
bool check(int x,int y,int k)//用来判断是否可以插入这个数
{
    //如果可以插入,满足两个条件
    //一是该行并且该列无此数k
    for(int i=0;i<9;i++)
    {
        if(s[x][i]==k+'0')
            return false;
        if(s[i][y]==k+'0')
            return false;
    }
    //二是该九框没有重复得数字
    //先找边界
    int xmin=x/3*3;
    int xmax=xmin+3;
    int ymin=y/3*3;
    int ymax=ymin+3;
    for(int xx=xmin;xx<xmax;xx++)
    {
        for(int yy=ymin;yy<ymax;yy++)
            if(s[xx][yy]==k+'0')
                return false;
    }
    return true;
}
void dfs(int x,int y)
{
    //退出条件只有一个:即每一级都填完了,成了第十行
    //因为从0开始,所以判定条件应该为>8
    if(x>8)//全部遍历完,直接输出直接输出
    {
        for(int i=0;i<9;i++)
        {
            for(int j=0;j<9;j++)
            {
               cout<<s[i][j];
            }
            cout<<endl;
        }
        return ;
    }
    //没遍历完分两种情况,
    //1.不为0,直接进行下一步遍历,因为是按行遍历,所以每一行遍历完,在进行下一行
    if(s[x][y]!='0')
        dfs(x+(y+1)/9,(y+1)%9);
    //2.为0,判断是否可以加入,注意每次加入之后还要再拿出来,因为每次相互独立
    if(s[x][y]=='0')
    {
        for(int i=1;i<=9;i++)//从1到9进行遍历
        {
            if(check(x,y,i))
            {
                s[x][y]='0'+i;//这个地方不能用标志变量,必须将其置为0,因为如果不是0的话,会当成已填数字处理
                dfs(x+(y+1)/9,(y+1)%9);
                s[x][y]='0';
            }
        }
    }
    //
}
int main()
{
    for(int i=0;i<9;i++)
        cin>>s[i];
    dfs(0,0);
    return 0;
}
例题:

如上面的10个格子,填入0~9的数字,不能重复(原先已经填了一部分数字),要求:连续的两个数字不能相邻(左右,上下,对角都算相邻)。例如:数字0和1不能放在一起。
问:一共有多少种可能的填数方案?

输入
输入多组测试数据。
每组测试数据有三行,第一行三个整数,第二行四个整数,第三行三个整数,之间用空格隔开,分别代表每个空格所填的数,如果原先没有数,则填-1。
输入数据保证不重复数字,不保证连续数字不相邻。

输出
每组测试数据输出一行。
输出表示方案数目的整数。
代码
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
using namespace std;
int a[3][4];//图表
bool fl[10];//是否被标记
int ans,k;//个数,是否输入的标记
void scan()//输入
{
    a[0][0]=-2;
    a[0][1]=k;
    for(int i=2;i<4;i++)
        cin>>a[0][i];
    for(int i=0;i<4;i++)
        cin>>a[1][i];
    for(int i=0;i<3;i++)
        cin>>a[2][i];
    a[2][3]=-2;
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<4;j++)
            if(a[i][j]>=0&&a[i][j]<=9)
            fl[a[i][j]]=true;
    }
}
bool check(int r,int c,int k)//判断重复数字
{
    //因为是相邻的,所以只需要看正上,左上,右上,左
    //不用看整行整列或是对角线,因为有可能不相邻
    //正上
    if(r>0&&abs(a[r-1][c]-k)==1)
        return false;
    //左上
    if(r>0&&c>0&&abs(a[r-1][c-1]-k)==1)
        return false;
    //右上
    if(r>0&&c<3&&abs(a[r-1][c+1]-k)==1)
        return false;
    //左
    if(c>0&&abs(a[r][c-1]-k)==1)
        return false;
    return true;
}
void dfs(int n)
//此时参数是1个,因为表格无顺序,只需要一个一个来,用已选择表的个数方便
{
    if(n==12)//终止条件,12个全ok了
    {
        ans++;
        return;
    }
    int r=n/4,c=n%4;//目标所在的行和列
    int k=a[r][c];//目标表格的值
    if(k==-1)
    {
        for(int kk=0;kk<=9;kk++)
        {
            if(fl[kk]==false&&check(r,c,kk))
            {
                fl[kk]=true;
                a[r][c]=kk;
                dfs(n+1);
                a[r][c]=-1;
                fl[kk]=false;//回溯之后不一定只有一个
            }
        }
    }
    else if(check(r,c,k))
    {
        dfs(n+1);
    }
}
int main()
{
    while(cin>>k)
    {
        memset(fl,false,sizeof(fl));//注意这个顺序,memset再输入前,如果放后面的话,本来有数字的地方会消失
        scan();
        ans=0;
        dfs(0);
        cout<<ans<<endl;
    }
}

寒假作业

现在小学的数学题目也不是那么好玩的。看看这个寒假作业:

□ + □ = □

□ - □ = □

□ × □ = □

□ ÷ □ = □

图1.jpg

(如果显示不出来,可以参见【图1.jpg】) 每个方块代表1~13中的某一个数字,但不能重复。

比如:

6 + 7 = 13

9 - 8 = 1

3 * 4 = 12

10 / 2 = 5

以及:

7 + 6 = 13

9 - 8 = 1

3 * 4 = 12

10 / 2 = 5

就算两种解法。(加法,乘法交换律后算不同的方案)

你一共找到了多少种方案?

请填写表示方案数目的整数。

注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。

//这个题的教训是每次改数的时候都别忘了再次初始化
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
bool fl[14];
int a[13],ans=0;
void dfs(int n)
{
    if(n==13)
    {
        ans++;
        return;
    }
    else if(n==3)
    {
        if(a[1]+a[2]>=1&&a[1]+a[2]<=13&&fl[a[1]+a[2]]==false)
        {
            a[3]=a[1]+a[2];
            fl[a[3]]=true;
            dfs(n+1);
            fl[a[3]]=false;
        }
        else
            return ;
    }
    else  if(n==6)
    {
        if(a[4]-a[5]>=1&&fl[a[4]-a[5]]==false&&a[4]-a[5]<=13)
        {
            a[6]=a[4]-a[5];
            fl[a[6]]=true;
            dfs(n+1);
            fl[a[6]]=false;
        }
        else
            return;
    }
    else if(n==9)
    {
        if(a[7]*a[8]<=13&&fl[a[7]*a[8]]==false&&a[7]*a[8]>=1)
        {
            a[9]=a[7]*a[8];
            fl[a[9]]=true;
            dfs(n+1);
            fl[a[9]]=false;
        }
        else
            return;
    }
    else if(n==12)//终止条件
    {
        if(a[10]%a[11]==0)
        {
            a[12]=a[10]/a[11];
        }
        else
            return;
        if(a[12]>=1&&a[12]<=13&&fl[a[12]]==false)
        {
            fl[a[12]]=true;
            dfs(n+1);
            fl[a[12]]=false;
        }
        else
            return;
    }
    else
        for(int i=1; i<=13; i++)
        {
            if(fl[i]==false)
            {
                fl[i]=true;
                a[n]=i;
                dfs(n+1);
                fl[i]=false;
            }
        }
}
int main()
{
    memset(fl,false,sizeof(fl));
    dfs(1);
    cout<<ans;
    return 0;
}
组合

排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r < = n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。
现要求你不用递归的方法输出所有组合。
例如n = 5 ,r = 3 ,所有组合为:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5 

输入

一行两个自然数n、r ( 1 < n < 21,1 < = r < = n )。

输出

所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,所有的组合也按字典顺序。

//题解:此题不同于组合
//组合是相同数字不同位置也可以
//排列必须实不同的数字
//所以只需要循环就可以,也是需要回溯法,因为不同的组合数字可能重复出先
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[25];
bool fl[25];
int n,r;
void dfs(int k)
{
    if(k==r)
    {
        for(int i=0;i<r;i++)
        {
            printf("%d",a[i]);
            if(i!=r-1)
                printf(" ");
            else
                printf("\n");
        }
        return;
    }
    for(int i=a[k-1]+1;i<=n;i++)//因为保证不重复,所以采用递增序,所以从前一个数加1开始
    {
        if(fl[i]==false)
        {
            fl[i]=true;
            a[k]=i;
            dfs(k+1);
            fl[i]=false;
        }
    }
}
int main()
{
    while(cin>>n>>r)
    {
        memset(fl,false,sizeof(fl));
        dfs(0);
    }
    return 0;
}

补充变式

描述找出从自然数1、2、... 、n(0<n<10)中任取r(0<r<=n)个数的所有组合。

输入
输入n、r。
输出
按特定顺序输出所有组合。
特定顺序:每一个组合中的值从大到小排列,组合之间按逆字典序排列。
样例输入
5 3
样例输出
543
542
541
532
531
521
432
431
421
321


例题

题目描述

已知 n 个整数b1,b2,…,bn

以及一个整数 k(k<n)。

从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。

例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:
    3+7+12=22  3+7+19=29  7+12+19=38  3+12+19=34。
  现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数:3+7+19=29。


输入

第一行两个整数:n , k (1<=n<=20,k<n) 
第二行n个整数:x1,x2,…,xn (1<=xi<=5000000) 

输出

一个整数(满足条件的方案数)。 

样例输入 Copy

4 3
3 7 12 19

样例输出 Copy

1
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define maxn 30
using namespace std;
int a[maxn];
int n,r,ans;
int sushu(int x)
{
    if(x==1)
        return 0;
    int q=(int)sqrt(1.0*x);
    for(int i=2;i<=q;i++)
    {
        if(x%i==0)    return 0;
    }
    return 1;
}
void dfs(int rr,int k,int summ)//组合的另一种形式
{
    if(rr==r&&sushu(summ))
    {
        ans++;
        return;
    }
    if(k>=n||rr>r)
        return;
    dfs(rr,k+1,summ);
    dfs(rr+1,k+1,summ+a[k]);
}
int main()
{
    while(cin>>n&&n)
    {
        ans=0;
        cin>>r;
        for(int i=0;i<n;i++)
            cin>>a[i];
        dfs(0,0,0);
        cout<<ans<<endl;
    }
    return 0;
}


注意:
因为迭代次数很多,如果超时的化,可以将cout换成printf
posted @ 2020-07-21 00:01  Joelin12  阅读(194)  评论(0)    收藏  举报