DFS理解和应用

目录

 

DFS(Depth First Search)

数塔问题

Prime Ring Problem - HDOJ 1016 / UVa 524 /(紫书P194例题7-4)

Zipper HDOJ - 1501(DFS+剪枝)

Lake Counting POJ - 2386

棋盘问题 POJ - 1321

水果消除 HNUSTOJ 


DFS(Depth First Search)

算法竞赛中的一个重要技巧,在许多题目里,用DFS有着神奇的作用。

利用栈这种数据结构来实现(找到的第一个解不一定是最优解,只是先序遍历最早的可行解)

案例解释:走迷宫

看到哪个方向可以走就走哪个,而且你没有办法分身,所以只能慢慢试探,不撞南山不回头。


数塔问题

题目描述:

输入一个三角形塔,从三角塔顶出发向下走,每个点都有不同的权值,走到那个点就获得对应的权值,求走到塔底的时候能够获得的权值的最大值。

Sample Input

4

5

8 4

3 6 9

7 2 9 5

Sample Output

28

先来一个最普通的代码。

///数塔 1.0
#include <stdio.h>
#include <iostream>
#include <set>
#include <cmath>
#include <sstream>
#define MAX 100010
using namespace std;

int n;
int a[200][200];
int DFS(int i,int j)
{
    if(i==n)
        return a[i][j];
    int x = DFS(i+1,j);
    int y = DFS(i+1,j+1);
    return max(x,y) + a[i][j];
}

int main()
{
    while(cin>>n)
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=i;j++)
                cin>>a[i][j];
        cout<<DFS(1,1)<<endl;
    }
    return 0;
}

这个做法很直观,但是每一次都要从头开始搜,有大量的重复计算,这样非常耗时,需要优化。

可以另外加一个vis数组来存储上一次计算的结果,这样就可以节省计算时间。

///数塔 2.0
#include <stdio.h>
#include <iostream>
#include <set>
#include <cmath>
#include <sstream>
#define MAX 100010
using namespace std;

int n;
int a[200][200];
int vis[200][200];
int DFS(int i,int j)
{
    if(vis[i][j]!=-1)
        return vis[i][j];
    if(i==n)///end of this road
        vis[i][j] = a[i][j];///save the result
    else
    {
        int x = DFS(i+1,j);
        int y = DFS(i+1,j+1);
        vis[i][j] = max(x,y) + a[i][j];
    }
    return vis[i][j];
}

int main()
{
    while(cin>>n)
    {
        memset(a,0,sizeof(a));
        memset(vis,0xff,sizeof(vis));
        for(int i=1;i<=n;i++)
            for(int j=1;j<=i;j++)
                cin>>a[i][j];
        cout<<DFS(1,1)<<endl;
    }
    return 0;
}

Prime Ring Problem - HDOJ 1016 / UVa 524 /(紫书P194例题7-4)

题目描述:输入一个正整数n,把整数1,2,3,...,n组成一个环,使得相邻两个整数之和均为素数,输出时从整数1开始逆时针排列。同一个环输出一次。(n<=16)

Sample Input

6 8

Sample Output

Case 1:
1 4 3 2 5 6
1 6 5 2 3 4


Case 2:
1 2 3 8 5 6 7 4
1 2 5 8 3 4 7 6
1 4 7 6 5 8 3 2
1 6 7 4 3 8 5 2

分析:最大的数是16,如果把所有可能结果都生成然后一个个试。。。肯定超时。

这里可以用DFS回溯,即用深度优先遍历解答树(可参考紫书的解释)

另外注意输出格式,案例之间有一个空行

///Prime Ring Problem
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;

int vis[25]={0};
int n,num[25]={0};

int is_prime(int k)///判断素数,也可以打表,那样更快
{
    int i;
    for(i=2;i<=sqrt(k);i++)
        if(k%i==0)
        return 0;
    return 1;
}
void DFS(int k)
{
    int i;
    if(k>n&&is_prime(num[n]+num[1]))///测试最后一个数和第一个数之和是否为素数
    {
        for(i=1;i<n;i++)
            printf("%d ",num[i]);
        printf("%d\n",num[i]);
    }
    else
    {
        for(i=2;i<=n;i++)///尝试放置每个数i
        {
            if(!vis[i]&&is_prime(i+num[k-1]))///i没有用过且与前一个数之和为素数
            {
                vis[i]=1;
                num[k]=i;
                DFS(k+1);
                vis[i]=0;
            }
        }
    }
}
int main()
{
    int cnt=1;
    while(cin>>n)
    {
        if(n<1||n>19)
            break;
        printf("Case %d:\n",cnt++);
        num[1]=1;
        DFS(2);
        printf("\n");
    }
    return 0;
}

Zipper HDOJ - 1501(DFS+剪枝)

题目描述:给出两个字符串,问能否在不改变字符串本身顺序的情况下,拆开重组成指定字符串,输出yes/no。

Sample Input

3 
cat tree tcraete 
cat tree catrtee 
cat tree cttaree

Sample Output

Data set 1: yes 
Data set 2: yes 
Data set 3: no

( ˙˘˙ )没想到吧,这题居然也能用DFS。

分析:从三个串的首元素开始,遇到一串/二串与目标串匹配的字母,继续向下DFS,如果这些可以到达目标串的尾元素,那么意味着前面的都匹配成功,所以这个结果是yes,否则no。

//Zipper HDOJ 1501
#include <cstdio>
#include <iostream>
using namespace std;

char a[209],b[209],c[409];
int vis[209][209]={0};
int OK=0;
void DFS(int i,int j,int k)
{
    if(vis[i][j]==1)///已经访问过,剪掉
        return;
    if(c[k]=='\0')///到指定字符串的结尾,说明之前的都匹配成功
    {
        OK=1;
        return;
    }
    if(a[i]!='\0'&&c[k]==a[i])//匹配成功
        DFS(i+1,j,k+1);
    if(b[j]!='\0'&&c[k]==b[j])//匹配成功
        DFS(i,j+1,k+1);
    vis[i][j]=1;///cut
}
int main()
{
    int n,i,j,cnt=0;
    cin>>n;
    while(n--)
    {
        OK=0;
        for(i=0;i<209;i++)///cut
            for(j=0;j<209;j++)
            vis[i][j]=0;
        scanf("%s%s%s",a,b,c);
        DFS(0,0,0);
        printf("Data set %d: %s\n",++cnt,OK?"yes":"no");
    }
    return 0;
}

Lake Counting POJ - 2386

题目描述:给出一张n*m的地图,求图中水洼的个数。水洼的大小不定,如果W的四周也有W,这些W组成一个大的水洼。

Sample Input

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

Sample Output

3

分析:找到‘W’的地,然后对其四周进行判断(DFS),并且把它附近全部变成‘.’,计算这样做的次数,即为池塘的个数。

//Lake Counting POJ2386
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
char farm[109][109];
int n,m;

void dfs(int x,int y)
{
    farm[x][y]='.';
    int i,j;
    int tx,ty;
    for(i=-1;i<=1;i++)
    {
        for(j=-1;j<=1;j++)
        {
            tx=x+i;
            ty=y+j;
            if((tx>=0&&tx<n)&&(ty>=0&&ty<m)&&farm[tx][ty]=='W')
                dfs(tx,ty);
        }
    }
}
int main(void)
{
    int i,j;
    while(cin>>n>>m)
    {
        int sum=0;
        getchar();
        for(i=0;i<n;i++)
        {
            for(j=0;j<m;j++)
                cin>>farm[i][j];
            getchar();
        }
        for(i=0;i<n;i++)
            for(j=0;j<m;j++)
            {
                if(farm[i][j]=='W')
                {
                    dfs(i,j);
                    sum++;
                }
            }
        cout<<sum<<endl;
    }
    return 0;
}

棋盘问题 POJ - 1321

题目描述:给出一个n*m的棋盘,‘#’可以放棋子,求满足所有放下的棋子都不在同一行同一列的情况有多少种。(类似于八皇后问题)

Sample Input

2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1

Sample Output

2
1

分析:用DFS,满足条件则往下一个位置继续,如果放下的棋子已经达到要求的个数,则方法+1。

//POJ - 1321
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
///notice that the '#' can be placed
int n,m;
int ans=0;
char map[10][10];
int vis[10];
void DFS(int x,int step)
{
    if(step == m)///found a way that work
    {
        ans++;
        return;
    }
    if(x==n)
        return;
    DFS(x+1,step);
    for(int j=0; j<n; j++)
    {
        if(vis[j]==0 && map[x][j]=='#')///这行这列都没放棋子
        {
            vis[j]=1;
            DFS(x+1,step+1);
            vis[j]=0;///for different situations
        }
    }
}
int main()
{
    while(cin>>n>>m)
    {
        if(n==-1 && m==-1)
            break;
        ans=0;
        memset(vis,0,sizeof(vis));
        for(int i=0; i<n; i++)
            scanf("%s",map[i]);
        DFS(0,0);
        printf("%d\n",ans);
    }
    return 0;
}

水果消除 HNUSTOJ 

题目描述:给出一个n*n的表格,表格元素是数字,相同的数字表示相同的水果,两个或以上相邻的水果可以消除,(消除之后其他水果不会改变位置),问可以消除的方案数。

Sample Input

6
1 1 2 2 2 2
1 3 2 1 1 2
2 2 2 2 2 3
3 2 3 3 1 1
2 2 2 2 3 1
2 3 2 3 2 2

Sample Output

6

分析:用DFS找某个数字周围的相同数字,如果相同就沿着这个数字向下继续找,如果相同的数字个数大于等于2,那么这样的一次搜索就是一个方案,搜索过的数字变成0,代表已经搜索过,最终输出方案数。

///水果消除 HNUSTOJ
#include <cstdio>
#include <iostream>
using namespace std;
///notice that the '#' can be placed while '.' not
int map[1000][1000];
int dir[4][2]= {{1,0},{-1,0},{0,1},{0,-1}};
int n;
int num;
void DFS(int x,int y,int k)
{
    map[x][y]=0;
    int i;
    int tx,ty;
    for(i=0;i<4;i++)
    {
        tx=x+dir[i][0];
        ty=y+dir[i][1];
        if(tx>=0 && tx<n && ty>=0 && ty<n && (map[tx][ty] == k))
        {
            num++;
            DFS(tx,ty,k);
        }
    }
}
int main()
{
    while(cin>>n)
    {
        int sum=0;
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                scanf("%d",&map[i][j]);
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                if(map[i][j])
                {
                    num=1;
                    DFS(i,j,map[i][j]);
                    if(num>1)
                         sum++;
                }
        printf("%d\n",sum);
    }
    return 0;
}

=======遇到好的题目会回来继续更新======

===如果各位大牛牪犇路过发现问题欢迎反映  (「・ω・)「嘿===

猜你喜欢

转载自blog.csdn.net/qq_41759198/article/details/81501764