Bootstrap

dfs算法总结,通过dfs解决洛谷中部分例题

一.dfs算法的介绍。

深度优先搜索算法(Depth First Search,简称DFS):一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况算法时间复杂度为O(!n)。

(简单来说就是一条路走到黑,直到走不动,再回头继续走,直到搜索完全部为止)

过程图如下:

 深度优先搜索,以深度为优先选项,它会走遍所有的路径,即枚举所有完整路径来找到最佳的路径。

二.例题分析。

一.迷宫问题。

对于迷宫问题属于非常经典的dfs例题,其解决思路很符合dfs搜索过程。

https://www.luogu.com.cn/problem/P1605

题目描述

给定一个 N×M 方格的迷宫,迷宫里有 T 处障碍,障碍处不可通过。

在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

给定起点坐标和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。

输入格式

第一行为三个正整数 N,M,T,分别表示迷宫的长宽和障碍总数。

第二行为四个正整数 SX,SY,FX,FY,SX,SY 代表起点坐标,FX,FY 代表终点坐标。

接下来 T 行,每行两个正整数,表示障碍点的坐标。

输出格式

输出从起点坐标到终点坐标的方案总数。

输入输出样例

输入 #1复制

2 2 1
1 1 2 2
1 2

输出 #1复制

1

说明/提示

对于 100%100% 的数据,1≤N,M≤5,1≤T≤10,1≤SX,FX≤n,1≤SY,FY≤m。

解决思路

对于本题没有太多的难点,就是dfs基本的解题思路。

定义二维数组a和book,分别用于存放地图和标记数组。

开一个方向数组,方便搜索

int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}};

边界条件

if(fx<1||fx>n||fy<1||fy>m)
            continue;

核心代码

for(int i=0;i<4;i++)
    {
        fx=x+next[i][0];
        fy=y+next[i][1];
        if(fx<1||fx>n||fy<1||fy>m)
            continue;
        if(book[fx][fy]==0&&a[fx][fy]==0)
        {
            book[fx][fy]=1;
            dfs(fx,fy);
            book[fx][fy]=0;
        }
    }

完整代码

#include<stdio.h>
int n,m,x1,y1,x2,y2,num=0;
int h,g;
int a[10][10];             //存储地图
int book[10][10]={0};      //标记作用                
void dfs(int x,int y)
{
    int fx,fy;
    int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}};       //方向数组
    if(x==x2&&y==y2)               //结束条件
    {
        num++;
        return ;
    }
    for(int i=0;i<4;i++)
    {
        fx=x+next[i][0];
        fy=y+next[i][1];
        if(fx<1||fx>n||fy<1||fy>m)     //边界条件
            continue;
        if(book[fx][fy]==0&&a[fx][fy]==0)
        {
            book[fx][fy]=1;           //标记
            dfs(fx,fy);
            book[fx][fy]=0;           //回溯
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    int k;
    scanf("%d",&k);
    scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
    for(int i=0;i<k;i++)
    {
        scanf("%d%d",&h,&g);
        a[h][g]=1;
    }
    book[x1][y1]=1;
    dfs(x1,y1);
    printf("%d\n",num);
    return 0;
}

二.填涂颜色。

本题就可以通过dfs解决又可以通过bfs解决,此处就dfs方法进行分析。

https://www.luogu.com.cn/problem/P1162

题目描述

由数字 0 组成的方阵中,有一任意形状的由数字 1 构成的闭合圈。现要求把闭合圈内的所有空间都填写成 2。例如:6×6 的方阵(n=6),涂色前和涂色后的方阵如下:

如果从某个 0 出发,只向上下左右 4 个方向移动且仅经过其他 00的情况下,无法到达方阵的边界,就认为这个 0 在闭合圈内。闭合圈不一定是环形的,可以是任意形状,但保证闭合圈内的 0 是连通的(两两之间可以相互到达)。

0 0 0 0 0 0
0 0 0 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 1 0 1
1 1 1 1 1 1
0 0 0 0 0 0
0 0 0 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 1 2 1
1 1 1 1 1 1

输入格式

每组测试数据第一行一个整数 n(1≤n≤30)。

接下来 n 行,由 0 和 1 组成的 n×n 的方阵。

方阵内只有一个闭合圈,圈内至少有一个0。

输出格式

已经填好数字 2 的完整方阵。

输入输出样例

输入 #1复制

6
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1

输出 #1复制

0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1

说明/提示

对于 100%100% 的数据,1≤n≤30。

解题思路

题目要求我们将被1围住的空间中的0全部改为2,但是直接按照这样写显得繁琐,那我们不妨逆向思维,将外面的0全部改为2,那么剩余为0的不就应该是被围起来的空间。最后我们们将0全部改为2,将3再改回0,输出地图就ok了。

思路理完代码就非常容易了。

这里有一个小技巧,我们可以将数组开的大比地图大一点,地图从(1,1)开始录入,这样就可以一搜到底了,不需要遍历地图,这样简化我们的过程。

过程就不多赘述了,直接上代码

完整代码

#include<stdio.h>
int n;
int a[100][100]={0};
int next1[4]={1,0,-1,0};                //方向数组
int next2[4]={0,1,0,-1};
void dfs(int x,int y)
{
    a[x][y]=3;
    int dx,dy;
    for(int i=0;i<4;i++)                  //核心代码都一样,就不过多解释了
    {
        dx=x+next1[i];
        dy=y+next2[i];
        if(dx<0||dx>n+1||dy<0||dy>n+1)
            continue;
        if(a[dx][dy]==0)
        {
            dfs(dx,dy);
        }
    }
    return;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)        //从1开始录入地图
    {
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&a[i][j]);
        }
    }
    dfs(0,0);                   //直接从原点开始搜索
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(a[i][j]==0)        //转换一下
                a[i][j]=2;
            if(a[i][j]==3)
                a[i][j]=0;
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

 三.八皇后 Checker Challenge。

https://www.luogu.com.cn/problem/P1219

最后我们来看一下比较复杂的八皇后问题,比较一下本题和前面两题的区别,总结经验。

题目描述

一个如下的 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 2 4 6 1 3 5来描述,第 i 个数字表示在第 i 行的相应位置有一个棋子,如下:

行号 1 2 3 4 5 6

列号 2 4 6 1 3 5

这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 3 个解。最后一行是解的总个数。

输入格式

一行一个正整数 n,表示棋盘是n×n 大小的。

输出格式

前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。

输入输出样例

输入 #1复制

6

输出 #1复制

2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4

说明/提示

【数据范围】
对于 100%100% 的数据,6≤n≤13。

题目翻译来自NOCOW。

USACO Training Section 1.5

对于主体部分我们仍然可以套用上面的核心代码,根据题意,两个皇后不能出现在同一行,列,对角线上,首先我们要知道在同一个对角线上怎么表示。

上对角线(hang-lie)行号-列号   (hang-lie+7)都为正数。

下对角线(hang+lie)行号+列号

知道这个就好办多了,然后我们为了方便通过定义4个一维数组,hang,lie,shang,xia分别代表四条线,然后我们一行一行的遍历,这样就可以通过一维数组实现对整个棋盘的搜索。

最后完整代码附上

#include<stdio.h>
int hang[15],lie[15],shang[100],xia[100];           //三个一维数组用来标记,看是否被占用
int n,num=0;
void dfs(int x)          //注意这里是一行一行的遍历
{
    if(x>n)            //遍历完棋盘
    {
        num++;         //统计有多少中方法
        if(num<=3)     //输出前三个
        {
            for(int i=1;i<=n;i++)
                printf("%d ",hang[i]);        //数组hang用来存储皇后的位置
            printf("\n");
        }
        return;
    }
    for(int i=1;i<=n;i++)               //核心代码,只是增加了判断条件
    {
        if(lie[i]==0&&shang[x-i+7]==0&&xia[i+x]==0)      //看是否被标记占用
        {
            hang[x]=i;
            lie[i]=1;
            shang[x-i+7]=1;              //标记
            xia[x+i]=1;
            dfs(x+1);
            lie[i]=0;                    //回溯
            shang[x-i+7]=0;
            xia[x+i]=0;
        }
    }
}
int main()
{
    scanf("%d",&n);
    dfs(1);              //从第一行开始遍历数组
    printf("%d\n",num);
    return 0;
}

三.总结。

对于dfs,可以用于多种情况,我们应合理的利用,在面对不同情景时,我们应该抓住核心,从多方面看待问题。dfs虽然可以解决很多问题,但求解过程相对暴力,在部分问题上不如bfs,以此,针对不同问题,找到适合的方法。

最后,这是目前我(新手小白)对于dfs一些粗陋的看法,其中肯定有一些错误,欢迎,接受批评建议,能够得到斧正。还是那句话,如果觉得错误过多,或者不认同,可以把这篇博客当做笑话。希望同大家一起进步!!!

如果dfs过程不明白的话可以看一下这篇博客

https://blog.csdn.net/m0_46549425/article/details/108025133?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22108025133%22%2C%22source%22%3A%22w20221013%22%7D&fromshare=blogdetail

八皇后问题的详细题解可以看这一篇(有图,方便理解)

https://blog.csdn.net/m0_46549425/article/details/108061923

;