DFS之连通性模型——AcWing 1112. 迷宫

DFS之连通性模型

定义

DFS(深度优先搜索,Depth-First Search)之连通性模型主要用于图论问题中判断图的连通性,即确定图中的所有节点是否可以通过边相互到达。

DFS(深度优先搜索,Depth-First Search)之连通性模型主要用于图论问题中判断图的连通性,即确定图中的所有节点是否可以通过边相互到达。

运用情况

  1. 判断图的连通性:检查一个无向图是否为连通图,只需从任一节点开始执行DFS,若能访问到所有节点,则图是连通的。
  2. 强连通分量:在有向图中,寻找所有强连通分量(即每个节点都可以到达其他所有节点的子图)。
  3. 迷宫问题:判断从起点到终点是否有路径,以及找出一条路径。
  4. 岛屿数量:在二维网格图中,计算由1(表示陆地)构成的“岛屿”数量。
  5. 社交网络分析:分析用户间的关系网,判断是否属于同一社交圈等。

注意事项

  1. 避免循环:需要标记已访问的节点,防止陷入无限循环。
  2. 起始节点选择:如果图可能不连通,需从每个未访问的节点开始执行DFS,以检测所有连通分量。
  3. 栈溢出:深度极大的图可能导致递归栈溢出,可考虑使用迭代方式或手动管理栈来避免。
  4. 性能考虑:对于大规模图,DFS可能不是最优选择,尤其是需要频繁遍历多个节点的情况。

解题思路

  1. 初始化:标记所有节点为未访问。
  2. 选择起始节点:通常从一个节点开始,但若考虑全图连通性,需对每个未访问节点执行DFS。
  3. DFS过程:访问当前节点,将其标记为已访问。遍历当前节点的所有邻居,对于每个未访问的邻居,递归执行DFS。在递归返回后,继续探索当前节点的下一个未访问邻居,直到所有邻居都被访问。
  4. 结果判断:如果从初始节点能够访问所有节点,图是连通的。若进行多轮DFS以探索不同连通分量,最终连通分量的数量可揭示图的整体连通性。

AcWing 1112. 迷宫

题目描述

AcWing 1112. 迷宫 - AcWing

运行代码

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

struct Point {
    int x;
    int y;
};

bool bfs(vector<vector<char>> &maze, Point start, Point end) {
    int n = maze.size();
    vector<vector<bool>> visited(n, vector<bool>(n, false));
    queue<Point> q;
    q.push(start);
    visited[start.x][start.y] = true;

    int dx[] = {-1, 1, 0, 0};
    int dy[] = {0, 0, -1, 1};

    while (!q.empty()) {
        Point curr = q.front();
        q.pop();

        if (curr.x == end.x && curr.y == end.y) {
            return true;
        }

        for (int i = 0; i < 4; i++) {
            int newX = curr.x + dx[i];
            int newY = curr.y + dy[i];

            if (newX >= 0 && newX < n && newY >= 0 && newY < n && maze[newX][newY] == '.' &&!visited[newX][newY]) {
                visited[newX][newY] = true;
                Point next = {newX, newY};
                q.push(next);
            }
        }
    }

    return false;
}

int main() {
    int k;
    cin >> k;

    while (k--) {
        int n;
        cin >> n;

        vector<vector<char>> maze(n, vector<char>(n));

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                cin >> maze[i][j];
            }
        }

        Point start, end;
        cin >> start.x >> start.y >> end.x >> end.y;

        if (maze[start.x][start.y] == '#' || maze[end.x][end.y] == '#') {
            cout << "NO" << endl;
            continue;
        }

        if (bfs(maze, start, end)) {
            cout << "YES" << endl;
        } else {
            cout << "NO" << endl;
        }
    }

    return 0;
}

代码思路

  • 定义了一个Point结构体来表示坐标点。
  • bfs函数用于进行广度优先搜索。
    • 创建一个与迷宫大小相同的visited数组来标记已访问的点。
    • 使用一个队列来存储待访问的点。
    • 定义了四个方向的偏移量。
    • 从起始点开始,将其入队并标记为已访问。
    • 只要队列不为空,取出队头元素,检查是否到达终点。如果未到达,则向四个方向扩展,将可通行且未访问的点入队并标记。
  • main函数中:
    • 输入测试数据的组数。
    • 对于每组数据,输入迷宫信息、起始点和终点。
    • 如果起始点或终点不可通行,直接输出"NO"。
    • 否则调用bfs函数进行搜索,根据结果输出"YES"或"NO"。

改进思路

  1. 数据输入和错误处理:可以添加更多的输入错误处理,例如检查输入的行和列坐标是否在合法范围内,以及输入的迷宫字符是否只有 . 和 # 。

  2. 空间优化:对于 visited 数组,可以使用位运算或者更紧凑的数据结构来减少空间占用,特别是当 n 较大时。

  3. 性能优化:在扩展新节点时,可以先判断新节点是否在迷宫范围内,避免不必要的数组越界检查。考虑使用双端队列(deque)代替队列,在某些情况下可能会提高性能。

  4. 代码可读性和可维护性:为关键的代码段添加更详细的注释,以提高代码的可理解性。将一些复杂的逻辑提取为单独的函数,使代码结构更清晰。

  5. 功能扩展:可以添加输出最短路径的功能,如果需要的话。

  6. 算法优化:考虑使用 A* 算法等更高效的寻路算法,可能会在某些情况下更快地找到路径。

其它代码

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;

int n;
char g[N][N];
int xa, ya, xb, yb;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
bool st[N][N];
bool dfs(int x, int y)
{
    if (g[x][y] == '#') return false;
    if (x == xb && y == yb) 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;
    cin >> t;
    while(t -- )
    {
        memset(st, 0, sizeof st);
        cin >> n;
        for(int i = 0; i < n; i ++ ) cin >> g[i];
        cin >> xa >> ya >> xb >> yb;
        int t = dfs(xa, ya);
        if(t) puts("YES");
        else puts("NO");
    }
}

代码思路

  1. 定义了一些常量和变量,包括迷宫的大小 n,迷宫的字符数组 g,起点和终点的坐标 xa, ya, xb, yb,以及方向偏移数组 dx 和 dy,还有标记是否访问过的二维数组 st
  2. dfs 函数用于深度优先搜索来判断是否存在从起点到终点的路径。
    • 如果当前位置是 #,则返回 false
    • 如果当前位置就是终点,返回 true
    • 标记当前位置已访问。
    • 遍历四个方向,如果新位置合法且未访问,递归调用 dfs 函数,如果返回 true 则直接返回 true
  3. 在 main 函数中:
    • 首先读入测试数据的组数 t 。
    • 对于每组数据,先清空访问标记,读入迷宫信息和起点终点坐标。
    • 调用 dfs 函数进行搜索,根据结果输出 YES 或 NO 。

改进思路

  1. 数据输入和错误处理:增加对输入数据的合法性检查,例如确保输入的坐标在合法范围内,输入的迷宫字符只有 . 和 # 。
  2. 性能优化:可以考虑使用剪枝策略来减少不必要的搜索。例如,如果当前位置到终点的曼哈顿距离大于已经搜索的深度,就可以提前返回。
  3. 代码结构和可读性:为关键代码添加更多注释,提高代码的可理解性。将深度优先搜索的部分提取为一个单独的函数,使 main 函数更加简洁清晰。
  4. 搜索算法选择:对于较大规模的迷宫,可以考虑使用广度优先搜索(BFS),因为 BFS 能保证找到的是最短路径,并且在某些情况下可能比 DFS 更快找到可行解。
  • 12
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
题目链接:https://www.acwing.com/problem/content/4948/ 题目描述: 给定一棵有 $n$ 个结点的树,结点从 $1$ 到 $n$ 编号,每个结点都有一个权值 $w_i$,现在有 $m$ 次操作,每次操作是将树中编号为 $x$ 的结点的权值加上 $y$,然后询问一些节点是否为叶子节点,如果是输出 $1$,否则输出 $0$。 输入格式: 第一行包含两个整数 $n$ 和 $m$。 第二行包含 $n$ 个整数,其中第 $i$ 个整数表示结点 $i$ 的初始权值 $w_i$。 接下来 $n-1$ 行,每行包含两个整数 $a$ 和 $b$,表示点 $a$ 和点 $b$ 之间有一条无向边。 接下来 $m$ 行,每行描述一次操作,格式为三个整数 $t,x,y$。其中 $t$ 表示操作类型,$t=1$ 时表示将编号为 $x$ 的结点的权值加上 $y$,$t=2$ 时表示询问编号为 $x$ 的结点是否为叶子节点。 输出格式: 对于每个操作 $t=2$,输出一个结果,表示询问的结点是否为叶子节点。 数据范围: $1≤n,m≤10^5$, $1≤w_i,y≤10^9$ 样例: 输入: 5 5 1 2 3 4 5 1 2 1 3 3 4 3 5 2 3 0 1 3 100 2 3 0 1 1 100 2 3 0 输出: 1 0 0 算法1: 暴力dfs,每次都重新遍历整棵树,时间复杂度 $O(nm)$ 时间复杂度: 最坏情况下,每次操作都要遍历整棵树,时间复杂度 $O(nm)$,无法通过此题。 算法2: 用一个 vector<int> sons[n+5] 来存储每个点的所有子节点,这样可以用 $O(n)$ 预处理出每个点的度数 $deg_i$,如果 $deg_i=0$,则 $i$ 是叶子节点,否则不是。 对于每个操作,只需要更新叶子节点关系的变化就可以了。如果某个节点的度数从 $1$ 变成 $0$,则该节点变成了叶子节点;如果某个节点的度数从 $0$ 变成 $1$,则该节点不再是叶子节点。 时间复杂度: 每次操作的时间复杂度是 $O(1)$,总时间复杂度 $O(m)$,可以通过此题。 C++ 代码: (算法2)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱姌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值