DFS之连通性模型
定义
DFS(深度优先搜索,Depth-First Search)之连通性模型主要用于图论问题中判断图的连通性,即确定图中的所有节点是否可以通过边相互到达。
DFS(深度优先搜索,Depth-First Search)之连通性模型主要用于图论问题中判断图的连通性,即确定图中的所有节点是否可以通过边相互到达。
运用情况
- 判断图的连通性:检查一个无向图是否为连通图,只需从任一节点开始执行DFS,若能访问到所有节点,则图是连通的。
- 强连通分量:在有向图中,寻找所有强连通分量(即每个节点都可以到达其他所有节点的子图)。
- 迷宫问题:判断从起点到终点是否有路径,以及找出一条路径。
- 岛屿数量:在二维网格图中,计算由1(表示陆地)构成的“岛屿”数量。
- 社交网络分析:分析用户间的关系网,判断是否属于同一社交圈等。
注意事项
- 避免循环:需要标记已访问的节点,防止陷入无限循环。
- 起始节点选择:如果图可能不连通,需从每个未访问的节点开始执行DFS,以检测所有连通分量。
- 栈溢出:深度极大的图可能导致递归栈溢出,可考虑使用迭代方式或手动管理栈来避免。
- 性能考虑:对于大规模图,DFS可能不是最优选择,尤其是需要频繁遍历多个节点的情况。
解题思路
- 初始化:标记所有节点为未访问。
- 选择起始节点:通常从一个节点开始,但若考虑全图连通性,需对每个未访问节点执行DFS。
- DFS过程:访问当前节点,将其标记为已访问。遍历当前节点的所有邻居,对于每个未访问的邻居,递归执行DFS。在递归返回后,继续探索当前节点的下一个未访问邻居,直到所有邻居都被访问。
- 结果判断:如果从初始节点能够访问所有节点,图是连通的。若进行多轮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"。
改进思路
-
数据输入和错误处理:可以添加更多的输入错误处理,例如检查输入的行和列坐标是否在合法范围内,以及输入的迷宫字符是否只有
.
和#
。 -
空间优化:对于
visited
数组,可以使用位运算或者更紧凑的数据结构来减少空间占用,特别是当n
较大时。 -
性能优化:在扩展新节点时,可以先判断新节点是否在迷宫范围内,避免不必要的数组越界检查。考虑使用双端队列(
deque
)代替队列,在某些情况下可能会提高性能。 -
代码可读性和可维护性:为关键的代码段添加更详细的注释,以提高代码的可理解性。将一些复杂的逻辑提取为单独的函数,使代码结构更清晰。
-
功能扩展:可以添加输出最短路径的功能,如果需要的话。
-
算法优化:考虑使用 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");
}
}
代码思路
- 定义了一些常量和变量,包括迷宫的大小
n
,迷宫的字符数组g
,起点和终点的坐标xa, ya, xb, yb
,以及方向偏移数组dx
和dy
,还有标记是否访问过的二维数组st
。 dfs
函数用于深度优先搜索来判断是否存在从起点到终点的路径。- 如果当前位置是
#
,则返回false
。 - 如果当前位置就是终点,返回
true
。 - 标记当前位置已访问。
- 遍历四个方向,如果新位置合法且未访问,递归调用
dfs
函数,如果返回true
则直接返回true
。
- 如果当前位置是
- 在
main
函数中:- 首先读入测试数据的组数
t
。 - 对于每组数据,先清空访问标记,读入迷宫信息和起点终点坐标。
- 调用
dfs
函数进行搜索,根据结果输出YES
或NO
。
- 首先读入测试数据的组数
改进思路
- 数据输入和错误处理:增加对输入数据的合法性检查,例如确保输入的坐标在合法范围内,输入的迷宫字符只有
.
和#
。 - 性能优化:可以考虑使用剪枝策略来减少不必要的搜索。例如,如果当前位置到终点的曼哈顿距离大于已经搜索的深度,就可以提前返回。
- 代码结构和可读性:为关键代码添加更多注释,提高代码的可理解性。将深度优先搜索的部分提取为一个单独的函数,使
main
函数更加简洁清晰。 - 搜索算法选择:对于较大规模的迷宫,可以考虑使用广度优先搜索(BFS),因为 BFS 能保证找到的是最短路径,并且在某些情况下可能比 DFS 更快找到可行解。