LOADING

加载过慢请开启缓存 浏览器默认开启

Recollection--DFS

2023/11/25 算法 算法

    最近在学javaweb, 把自己博客给忘得差不多了,于是水篇文章证明我还活着()

DFS(深度优先搜索)

概述

    深度优先搜索 (Depth-first-search) 是一种图的算法, 以深度作为方向进行搜索,找到所需要的答案, 直到所有方案搜索完为止,其本质依然是暴力。

    走入迷宫, 向一条路径一直走下去直到走到死路为止, 再回返到最近的一个路口走另一条没有走过的路,依次类推…
    实现dfs的核心是标记回溯

模版

 void dfs(int x){
    if(满足停止搜索条件){
        ...        //处理
        return;
    }
    for (int i = 0; i < length; i++){
        if (没有搜过该点){
            vis[x] = 1; //标记搜过
            dfs(x + 1); //递归
            vis[x] = 0; //取消标记(回溯)
        }
    }
}

dfs变化多样,不一定就要照着模版写。

题目尝试

C语言新手, 请多多指教。

CF6A Triangle

    这是一道入门题, 数据少也不卡时间, 可以直接通过暴力枚举得到结果简单方便, 但还是用dfs来练练手()

    我们可以先将所有木棍从小到大排序,当我们任意取出3根木棍时,这3根木棍的长度一定是有序的,判断三角形时只需要将1、2号木棍与3号木棍长度进行比较即可。

AC代码:

#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
//直接调用C++的sort排序,懒得写排序了。 
//arr进行数据读入,arr1放入需判断的三根木棍, con代表状态。 
int arr[4], arr1[4], i, con = 0;
void dfs(int cur, int w)//cur代表已读入木棍数,该值等于3则开始判断三角形情况。 
{
    if (cur == 3)
    {
        //大于为三角形,等于为退化三角形 
        if(arr1[0] + arr1[1] > arr1[2])
        {
            con = 1;
        }
        else if (con == 0 && arr1[0] + arr1[1] == arr1[2])	//为什么要判断con==0呢 
        {
            con = 2;
        }
    }
    else
    {
/*
    w表示当前搜索到的位置
    我们从头开始理,最开始主程序调用dfs时:
    1.w = 0, cur = 0, 会进入到这里,j = 0开始循环, arr1放入0号元素, 递归;
    2.w = 1, cur = 1, 依然进入到这里, j = 1开始循环, arr1放入1号元素, 递归; 
    3.w = 2, cur = 2, 依然进入到这里, j = 2开始循环, arr1放入2号元素, 递归; 
    4.cur = 3, 判断条件,这里结束后将返回到第3步;
    5.第3步此时位于j = 2的循环中,dfs结束后,j++后变为3, arr1将不再放入2号元素而是放入3号元素,递归;
    6.cur = 3, 判断条件,这里结束后将返回到第3步;
    7.第3步到达循环末尾,再回到第2步,第2步j++后变为2,取3号元素,以此类推...
    8.你应该能理解为什么要写循环了。 
*/
        for (int j = w; j < 4; j++)
        {
            arr1[cur] = arr[j];		//放入arr1数组 
            dfs(cur + 1, j + 1);	//当前数量+1并递归搜索 
        }
    }
}
int main(void)
{
    for (i = 0; i < 4; i++)
    {
        scanf("%d", &arr[i]);
    }
    sort(arr, arr + 4);	//快排 
    dfs(0,0);	//dfs开始搜索 
    if (con == 1)
    {
        printf("TRIANGLE");
    }
    else if (con == 0)
    {
        printf("IMPOSSIBLE");
    }
    else
    {
        printf("SEGMENT");
    }
}

CF629A Far Relative’s Birthday Cake

    解释解释到底说了什么:只要在一行或一列上找到一对CC,就得到一点幸福指数,求总幸福指数。

    我们使用dfs进行搜索时带上一个数组arr1,用它来标记是否搜索过该元素,递归完成后取消标记,这就是标记与回溯。

AC代码:

#include<stdio.h>

const int MAX = 110;
int n, arr[MAX][MAX] = {0}, arr1[MAX][MAX], count = 0, i, j;

void dfs(int a, int b){
    //判断条件:x轴上的b小于n或y轴上的a小于n
    if ((a == i && b < n) || (b == j && a < n))
    {
        if (arr[a][b] == 1 && arr1[a][b] != 1)
        {
            //标记解 
            count++;
        }
        //往下和右递归调用 
        dfs(a+1, b);
        dfs(a,b+1);
    }
}
int main(void)
{
    //读入,将str数组转化为arr数组
    scanf("%d\n", &n);
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < n; j++)
        {
            char temp;
            scanf("%c", &temp);
            if (temp == 'C')
            {
                arr[i][j] = 1;
            }
            else if (temp == '.'){
                arr[i][j] = 0;
            }
        }
        if (i != n - 1)
        {
            scanf("\n");
        }
    }
    //只是把dfs的循环拿出来用了,原理一致。
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < n; j++)
        {
            if (arr[i][j] == 1)
            {
            //标记与回溯
            arr1[i][j] = 1;
            dfs(i, j);
            arr1[i][j] = 0;	
            }		
        }
    }
    printf("%d", count);
}

P1706 全排列问题

    经典题,与上题思想一致,使用dfs深度搜索,用另一个数组去标记是否搜索过该数。

    与第一道题不同, 我们所搜索的元素只可能在当前位置之后, 所以直接从当前位置循环, 而此题的排列没有任何顺序可言,我们从数组中遍历没有被标记的元素来作为下一个应该排的位置,所以区别还是挺大的()

AC代码:

#include<stdio.h>

int a[10], v[10], n;
void dfs(int t){
    if (t == n + 1)
    {
        for (int i = 1; i <= n; i++)
        {
            printf("    %d", a[i]);
        }
        printf("\n");
        return;
    }
    for (int i = 1; i <= n; i++)
    {
        if(v[i] != 1)
        {
            v[i] = 1;
            a[t] = i;
            dfs(t + 1);
            v[i] = 0;
        }
    }
}
int main(void)
{
    scanf("%d", &n);
    dfs(1);
}

P1451 求细胞数量

    连通块问题,只要连通的区域统统都算1个,直接dfs,将要搜索的设为1,dfs搜索到的区域统统变为0,注意递归时是朝东南西北四个方向进行搜索, 当触碰到边界时停止搜索。

AC代码:

#include<stdio.h>
const int MAX = 110;
int m, n;
int arr[MAX][MAX];
int count = 0;
//方向数组,分别对应东南西北。
int dx[4] = {0,0,1,-1}; 
int dy[4] = {1,-1,0,0};
void dfs(int a, int b)
{
    arr[a][b] = 0;
    for (int i = 0; i < 4; i++)
    {
        a += dx[i];
        b += dy[i];
        //判断搜索条件
        if (a <= m && a >= 1 && b <= n && b >= 1 && arr[a][b] == 1)
        {
            dfs(a, b);
        }
        a -= dx[i];
        b -= dy[i];
    }
}
int main(void)
{
    scanf("%d%d\n",&m,&n);
    char temp;
    for (int i = 1; i <= m; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            scanf("%c", &temp);
            if (temp <= '9' && temp > '0')
            {
                arr[i][j] = 1;
            }
            else
            {
                arr[i][j] = 0;
            }
        }	
        if (i != m)
        {
            scanf("\n");
        }
    }
    for (int i = 1; i <= m; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            if (arr[i][j] == 1)
            {
                dfs(i, j);
                //得到其一解
                ++count;
            }
        }
    }
    printf("%d", count);
}

P1506 拯救oibh总部

    如果你依然想套用上一题并信心满满的提交时,你会收到全WA的惊喜(我也一样233)

    其实我们可以在最外围搜一圈,凡是被洪水淹没的地段都设为1,最后循环查找没被淹没的地方(值为0的地方)就行了()

AC代码:

#include<stdio.h>
const int MAX = 510;
int arr[MAX][MAX], m, n, count = 0;
//方向数组
int ax[4] = {0, 0, 1, -1};
int ay[4] = {1, -1, 0, 0};
void dfs(int x, int y)
{
    int x1, y1;
    arr[x][y] = 1;
    for (int i = 0; i < 4; i++)
    {
        x1 = x + ax[i];
        y1 = y + ay[i];
        if (x1 >= 0 && x1 < n && y1 >=0 && y1 < m && arr[x1][y1] == 0)
        {
            dfs(x1, y1);
        }
    }
}
int main(void)
{
    //读入
    scanf("%d%d\n", &n, &m);
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            char temp;
            scanf("%c", &temp);
            if (temp == '0')
            {
                arr[i][j] = 0;
            }
            else{
                arr[i][j] = 1;
            }
        }
        if (i != n - 1)
        {
            scanf("\n");
        }
    }
    //在最外面一圈进行搜索
    for (int i = 0; i < m; i++)
    {
        if (arr[0][i] == 0)
        {
            dfs(0, i);
        }
        if (arr[n - 1][i] == 0)
        {
            dfs(n - 1, i);
        }
    }
    for (int i = 0; i < n; i++)
    {
        if (arr[i][0] == 0)
        {
            dfs(i, 0);
        }
        if (arr[i][m - 1] == 0)
        {
            dfs(i, m - 1);
        }
    }
    //统计没有被淹没的地段
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            if (arr[i][j] == 0)
            {
                count++;
            }
        }
    }
    printf("%d", count);
}

P1036 [NOIP2002 普及组] 选数

    此题跟其他dfs题如出一辙,可以说照着模版写就是了,唯一可能卡的点是判断是否为质数:
int prime(int sum)
{
    for (int i = 2; i * i <= sum; i++)
    {
        if (sum % i == 0)
        {
            return 0;
        }
    }
    return 1;
}

dfs部分不作展示。

[USACO10OCT] Lake Counting S

    也是一般dfs思路,唯一不同的地方在于为八个方向,只需要将方向数组修改就行了。
    //东、东南、南、西南、西、西北、北、东北
    int ax[9] = {0, 0, 1, 1, 1, 0, -1, -1, -1};
    int ay[9] = {0, 1, 1, 0, -1, -1, -1, 0, 1};

dfs部分不作展示。

[USACO1.5] 八皇后 Checker Challenge

    喜欢我八皇后吗,说实话,真的没一点思路。

    在这个棋盘上,行列对角线上只能存在一个棋子,我写了点。

AC代码:

#include<stdio.h>
#include<math.h>
//分别对应行,主对角线,副对角线,答案数组。
int col[14], d1[30], d2[50], ans[14];
int n, w = 0;

void dfs(int x)
{
    if (x > n)
    {
        //题只要求输出前3种情况,所以w<3,随后只让解的个数增加
        if (w < 3)
        {
            for (int i = 1; i <= n; i++)
            {
                printf("%d ", ans[i]);
            }
            printf("\n");
        }
        w++;
    }
    else
    {
        for (int k = 1; k <= n; k++)
        {
            //平行的主对角线满足该特征:它们的行与列之差固定,但可能为负值,故再加上一个n。
            int z = x + k - 1;
            //平行的副对角线满足该特征:它们的行与列之和固定。
            int t = x - k + n;
            if (!col[k] && !d1[z] && !d2[t])
            {
                ans[x] = k;col[k] = 1;d1[z] = 1;d2[t] = 1;
                dfs(x + 1);
                col[k] = 0;d1[z] = 0;d2[t] = 0;
            }
        }	
    }
}
int main(void)
{
    scanf("%d", &n);
    dfs(1);
    printf("%d", w);
}
    这个题的意思是给你一个初始地与N个方向,N个方向都要按顺序使用,寻找到它最终的落脚点,这题DFS和BFS都可以用

    参考:dingcx:P1189 SEARCH
    考虑直接使用dfs会超时,我们选择优化,因为在dfs搜索过程中会出现多条路都通向一个节点的情况,我们在标记数组中加入方向长度减少这种情况,就进行优化,详细请读一读上面这篇文章~

AC代码:

#include<stdio.h>
const int MAX = 55;
const int MAXN = 1010;
int m, n, arr[MAX][MAX], arr1[MAXN][MAX][MAX], stx, sty, N, dir[MAXN];
int f[5][2] = {0,0,0,1,1,0,0,-1,-1,0};

void dfs(int len, int x, int y)
{
    if (arr1[len][x][y] == 1)
    {
        return;
    }
    else
    {
        int x1 = x, y1 = y;
        arr1[len][x][y] = 1;
        if (len == 0)
        {
            return;
        }
        while (1)
        {
            x1 += f[dir[len]][0];
            y1 += f[dir[len]][1];
            if (arr[x1][y1] == 1 || x1 < 0 || x1 >= m || y1 < 0 || y1 >= n)
            {
                break;
            }
            dfs(len - 1, x1, y1);
        }
    }
}
int main(void)
{
    scanf("%d%d\n", &m, &n);
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            char temp;
            scanf("%c", &temp);
            if (temp == 'X')
            {
                arr[i][j] = 1;
            }
            else if (temp == '.')
            {
                arr[i][j] = 0;
            }
            else{
                stx = i;
                sty = j;
                arr[i][j] = 0;
            }
        }
        if (i != m - 1)
        {
            scanf("\n");
        }
    }
    scanf("%d\n", &N);
    for (int i = 1; i <= N; i++)
    {
        char str[10];
        scanf("%s", str);
        if (str[0] == 'E')
        {
            dir[N + 1 - i] = 1;
        }
        else if (str[0] == 'S')
        {
            dir[N + 1 - i] = 2;
        }
        else if (str[0] == 'W')
        {
            dir[N + 1 - i] = 3;
        }
        else if (str[0] == 'N')
        {
            dir[N + 1 - i] = 4;
        }
    }
    dfs(N, stx, sty);
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            if (arr1[0][i][j] == 1)
            {
                printf("*");
            }
            else if (arr[i][j] == 0)
            {
                printf(".");
            }
            else{
                printf("X");
            }
        }   
        printf("\n");
    }
}

春空舞う花びらは,
過ぎ去った奇跡じゃなく,
巡った季節を超えても輝いて,
二人の未来を彩るよ。