案例6-1.4 地下迷宫探索
分数 30
全屏浏览
切换布局
作者 DS课程组
单位 浙江大学
地道战是在抗日战争时期,在华北平原上抗日军民利用地道打击日本侵略者的作战方式。地道网是房连房、街连街、村连村的地下工事,如下图所示。
我们在回顾前辈们艰苦卓绝的战争生活的同时,真心钦佩他们的聪明才智。在现在和平发展的年代,对多数人来说,探索地下通道或许只是一种娱乐或者益智的游戏。本实验案例以探索地下通道迷宫作为内容。
假设有一个地下通道迷宫,它的通道都是直的,而通道所有交叉点(包括通道的端点)上都有一盏灯和一个开关。请问你如何从某个起点开始在迷宫中点亮所有的灯并回到起点?
输入格式:
输入第一行给出三个正整数,分别表示地下迷宫的节点数N(1<N≤1000,表示通道所有交叉点和端点)、边数M(≤3000,表示通道数)和探索起始节点编号S(节点从1到N编号)。随后的M行对应M条边(通道),每行给出一对正整数,分别是该条边直接连通的两个节点的编号。
输出格式:
若可以点亮所有节点的灯,则输出从S开始并以S结束的包含所有节点的序列,序列中相邻的节点一定有边(通道);否则虽然不能点亮所有节点的灯,但还是输出点亮部分灯的节点序列,最后输出0,此时表示迷宫不是连通图。
由于深度优先遍历的节点序列是不唯一的,为了使得输出具有唯一的结果,我们约定以节点小编号优先的次序访问(点灯)。在点亮所有可以点亮的灯后,以原路返回的方式回到起点。
输入样例1:
6 8 1
1 2
2 3
3 4
4 5
5 6
6 4
3 6
1 5
输出样例1:
1 2 3 4 5 6 5 4 3 2 1
输入样例2:
6 6 6
1 2
1 3
2 3
5 4
6 5
6 4
输出样例2:
6 4 5 4 6 0
问题分析
- 迷宫节点和边:
- 节点表示灯和开关。
- 边表示通道,节点之间的直接连通关系。
- 目标:
- 从指定起点出发,遍历所有节点(点亮灯)。
- 如果无法遍历所有节点,输出部分遍历路径并以
0
结束。
- 路径唯一性:
- 采用深度优先搜索(DFS),并在访问时优先选择编号最小的邻居节点。
解题步骤
- 输入数据:
- 读取节点数 NNN、边数 MMM、起始节点 SSS。
- 使用邻接表存储边的关系,并对每个节点的邻居列表排序。
- DFS 遍历:
- 从起始节点 SSS 开始,访问所有能访问的节点。
- 记录路径,同时注意在回退时也记录路径(形成回路)。
- 连通性检查:
- 如果所有节点都被访问过,说明是连通图,输出完整路径。
- 否则,输出部分路径并以
0
结束。
- 实现优先访问规则:
- 使用排序保证节点编号小的邻居优先访问。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int>> adj; // 邻接表存储图
vector<bool> visited; // 标记是否访问过
vector<int> path; // 存储访问路径
// 深度优先搜索
void dfs(int node) {
visited[node] = true; // 标记当前节点已访问
path.push_back(node); // 记录访问路径
// 按小编号优先顺序访问邻居
for (int neighbor : adj[node]) {
if (!visited[neighbor]) {
dfs(neighbor); // 递归访问
path.push_back(node); // 回退时记录路径
}
}
}
int main() {
int N, M, S;
cin >> N >> M >> S;
adj.resize(N + 1); // 节点编号从 1 到 N
visited.resize(N + 1, false);
// 输入边信息,构建邻接表
for (int i = 0; i < M; ++i) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
// 对邻接表进行排序,保证小编号优先
for (int i = 1; i <= N; ++i) {
sort(adj[i].begin(), adj[i].end());
}
// 从起始节点开始 DFS
dfs(S);
// 检查是否所有节点都被访问过
bool allVisited = true;
for (int i = 1; i <= N; ++i) {
if (!visited[i]) {
allVisited = false;
break;
}
}
// 输出路径
for (int node : path) {
cout << node << " ";
}
// 如果不是连通图,最后输出 0
if (!allVisited) {
cout << "0";
}
return 0;
}
代码关键点解析
- 邻接表排序:
- 通过
sort
保证邻接表的访问顺序。
- 通过
- 路径回退:
- 每次递归返回时,将当前节点重新加入路径,形成闭合回路。
- 连通性判断:
- 检查
visited
数组是否所有节点都被访问过。
- 检查
时间复杂度
- 邻接表构建:O(M)O(M)O(M)
- 邻接表排序:O(NlogN)O(N \log N)O(NlogN)(每个节点排序)
- DFS 遍历:O(N+M)O(N + M)O(N+M)
- 总体复杂度:O(N+M+NlogN)O(N + M + N \log N)O(N+M+NlogN)
空间复杂度
- 邻接表:O(N+M)O(N + M)O(N+M)
- 访问标记数组:O(N)O(N)O(N)
- 路径存储:O(N)O(N)O(N)