第三章 搜索(2):DFS

本文深入探讨了深度优先搜索(DFS)在解决连通性模型、搜索顺序模型、剪枝与优化模型等问题中的应用。通过具体题目如迷宫、全排列、数独等,详细阐述了DFS的策略、剪枝优化方法以及如何结合其他技术如迭代加深(IDDFS)、双向DFS等提高效率。
摘要由CSDN通过智能技术生成

关于回溯:先dfs再改变当前状态不需要恢复现场,先改变当前状态再dfs则需要恢复现场
换一种解释:

  • 一种将DFS过程看成是在棋盘体内部进行搜索,从一个点搜索到另一个点,为了保证每个点仅能搜索一次,是不能恢复现场的;
  • 一种将DFS过程看成是把棋盘看成一个整体,从一个状态搜索到另一个状态,那么就需要先恢复状态才能继续搜索下一个状态。

代码模板

const int N = 100010, M = 2 * N;

int h[N], e[M], ne[M], idx;
bool st[N]; // 记录每个结点是否被搜索过

// 从第u个结点开始搜索,通常u取1
void dfs(int u) {
   
    st[u] = true; // 标记被搜索
    for (int i = h[u]; i != -1; i = ne[i]) {
   
        int j = e[i]; // 保存当前结点对应图里面的编号
        if (!st[j]) dfs(j);
    }
} 

1、连通性模型

DFS 只能求出是否连通,但是不能求最短路。

1.1 迷宫

ACwing 1112

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 110;

int n,  x1, y1, x2, y2;
char g[N][N];
bool st[N][N];
int dx[] = {
   0, -1, 0, 1}, dy[] = {
   -1, 0, 1, 0};

bool dfs(int x, int y) {
   
    if (g[x][y] == '#') return false; // 这个放到for里面会漏掉初始或者结束为#的情况,并且必须放在 dfs 第一行
    if (x == x2 && y == y2) return true;
    st[x][y] = true;
    for (int i = 0; i < 4; i++) {
   
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= n || b < 0 || b >= n) continue;
        if (st[a][b]) continue;
        if (dfs(a, b)) return true;
    }
    return false;
}

int main() {
   
    int T; scanf("%d", &T);
    while (T--) {
   
        scanf("%d", &n);
        for (int i = 0; i < n; i++) scanf("%s", g[i]);
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        memset(st, 0, sizeof st);
        if (dfs(x1, y1)) puts("YES"); else puts("NO");
    }
    return 0;
}

1.2 红与黑

ACwing 1113

注意:本题先输入列数,后输入行数

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 25;

int n, m;
char g[N][N];
bool st[N][N];
int dx[] = {
   0, -1, 0, 1}, dy[] = {
   -1, 0, 1, 0};

inline int dfs(int x, int y) {
   
    int cnt = 1;
    st[x][y] = true;
    for (int i = 0; i < 4; i++) {
   
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= n || b < 0 || b >= m) continue;
        if (g[a][b] != '.' || st[a][b]) continue;
        cnt += dfs(a, b);
    }
    return cnt;
}

int main() {
   
    while (scanf("%d%d", &m, &n), m || n) {
   
        memset(st, 0, sizeof st);
        for (int i = 0; i < n; i++) scanf("%s", g[i]);
        int x, y;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                if (g[i][j] == '@') {
   
                    x = i, y = j; break;
                }
        printf("%d\n", dfs(x, y));
    }
    return 0;
}

1.3 通信网络

ACwing 3250

开两个布尔数组 s t 1 、 s t 2 st1、st2 st1st2,分别表示能够从当前点能够走到的所有点以及能走到当前点的点(从该点开始沿着反向边遍历即可)。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 1010, M = 20010;

int n, m;
int h1[N], h2[N], e[M], ne[M], idx;
bool st1[N], st2[N];

void add(int h[], int a, int b) {
   
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void dfs(int u, int h[], bool st[]) {
   
    st[u] = true;
    for (int i = h[u]; i != -1; i = ne[i]) {
   
        int j = e[i];
        if (!st[j]) dfs(j, h, st);
    }
}

int main() {
   
    scanf("%d%d", &n, &m);
    memset(h1, -1, sizeof h1); memset(h2, -1, sizeof h2);
    while (m--) {
   
        int a, b; scanf("%d%d", &a, &b);
        add(h1, a, b), add(h2, b, a);
    }
    int res = 0;
    for (int i = 1; i <= n; i++) {
   
        memset(st1, 0, sizeof st1); memset(st2, 0, sizeof st2);
        dfs(i, h1, st1); dfs(i, h2, st2);
        int s = 0;
        for (int j = 1; j <= n; j++)
            if (st1[j] || st2[j]) s++;
        if (s == n) res++;
    }
    printf("%d\n", res);
    return 0;
}

1.4 树的重心

ACwing 846

Tips:dfs 可以计算出每个结点子树的大小

注意:

  • s u m sum sum:记录当前子树的大小,这里根节点算一个
  • r e s res res:表示将这个点删除之后每个连通块结点数的最大值
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 1e5 + 10, M = N * 2;

int n;
int h[N], e[M], ne[M], idx;
bool st[N];
int ans = N;

void add(int a, int b) {
   
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 返回以u为根结点的树的子树结点个数
int dfs(int u) {
   
    st[u] = true;
    int sum = 1, res = 0;
    for (int i = h[u]; i != -1; i = ne[i]) {
   
        int j = e[i];
        if (!st[j]) {
   
            int s = dfs(j); // 表示当前子树的大小
            res = max(res, s); // 可以求出根结点的子树的连通块中结点数最大值
            sum += s;
        }
    }
    res = max(res, n - sum); // 除去结点u及其子树剩余结点构成的一个连通块中的数量
    ans = min(ans, res);
    return sum;
}

int main() {
   
    memset(h, -1, sizeof h);
    scanf("%d", &n);
    for (int i = 0; i < n - 1; i++) {
   
        int a, b; scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }
    dfs(1);
    printf("%d\n", ans);
    return 0;
}

1.5 网络延时(树的直径)

ACwing 3215

求将每个点作为最高结点的时候,它到其所有儿子结点的最长距离和次长距离,该值就是以该节点为最高结点的最长路径。

时间复杂度: O ( n ) O(n) O(n)

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 20010;

int n, m;
int h[N], e[N], ne[N], idx;
int ans;

inline void add(int a, int b) {
   
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

inline int dfs(int u) {
   
    int d0 = 0, d1 = 0; // 当前结点下的最大长度和次大长度
    for (int i = h[u]; ~i; i = ne[i]) {
   
        int j = e[i];
        int d = dfs(j);
        if (d > d0) d1 = d0, d0 = d;
        else if (d > d1) d1 = d;
    }
    ans = max(ans, d0 + d1);
    return d0 + 1; // +1 表示当前结点往父结点走的边
}

int main() {
   
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for (int i = 2; i <= n + m; i++) {
   
        int p; scanf("%d", &p);
        add(p, i);
    }
    dfs(1);
    printf("%d\n", ans);
    return 0;
}

1.6 I’m stuck!

ACwing 3196

两次DFS,第一次DFS找到所有能够从起点走到的点,用数组 s t 1 st1 st1记录,第二次DFS找到所有能够从终点走到的点,用数组 s t 2 st2 st2记录。最终判断点 ( i , j ) (i,j) (i,j)是否满足条件 s t 1 [ i ] [ j ] = t r u e   & &   s t 2 [ i ] [ j ] = f a l s e st1[i][j] = true\ \&\&\ st2[i][j] = false st1[i][j]=true && st2[i][j]=false即可。

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 55;

int n, m;
char g[N][N];
bool st1[N][N], st2[N][N];
int dx[] = {
   -1, 0, 1, 0,}, dy[] = {
   0, 1, 0, -1};

inline bool check(int x, int y, int k) {
   
    char c = g[x][y];
    if (c == '+' || c == 'S' || c == 'T') return true;
    if (c == '|' && (k == 0 || k == 2)) return true;
    if (c == '-' && (k == 1 || k == 3)) return true;
    if (c == '.' && k == 2) return true;
    return false;
}

inline void dfs1(int x, int y) {
   
    st1[x][y] = true;
    for (int i = 0; i < 4; i++) {
   
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= n || b < 0 || b >= m) continue;
        if (st1[a][b] || g[a][b] == '#') continue;
        if (check(x, y, i)) dfs1(a, b);
    }
}

inline void dfs2(int x, int y) {
   
    st2[x][y] = true;
    for (int i = 0; i < 4; i++) {
   
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= n || b < 0 || b >= m) continue;
        if (st2[a][b] || g[a][b] == '#') continue;
        if (check(a, b, i ^ 2)) dfs2(a, b);
    }
}

int main() {
   
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) scanf("%s", g[i]);
    int x, y;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (g[i][j] == 'S') dfs1(i, j);
            else if (g[i][j] == 'T') {
   
                x = i, y = j; dfs2(i, j);
            }
    if (!st1[x][y]) puts("I'm stuck!");
    else {
   
        int res = 0;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                if (st1[i][j] && !st2[i][j]) res++;
        printf("%d\n", res);
    }
    return 0;
}

2、搜索顺序模型

2.1 排列数字

ACWing 842

对比第一章 10.1 ACwing 92. 递归实现指数型枚举

遍历过程: 如下图,注意,dfs(0)对应图中第一层,dfs(1)对应图中第二层,依次类推。
DFS遍历过程

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 10;

int n, path[N];
bool st[N];

void dfs(int u) {
   
    if (u == n) {
   
        for (int i = 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值