原题目:图的遍历 - 洛谷
原题目截图:
思路分析:
一说到图的遍历,大家很容易就联想到BFS或者DFS算法,但是如果你试过这题,你就会发现这题有点小坑。我一开始很想当然的对每一个点进行BFS,这个思路最简单,但是显然数据量过大,案例会超时。然后我改成DFS方法,但是因为图中可能是有环路的图,因此当DFS的时候,有些边可能遍历不到。最后我想到了反向建图的BFS方法。
对每一个点进行BFS,案例超时,代码如下:
//对每个点进行正向BFS,部分案例超时
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
struct edge {
int u, v;
bool flag = false; // 标记这条边是否访问过
};
int bfs(int first, vector<vector<int>>& graph, int n) {
vector<int>visit(n + 1, 0);
queue<int>que;
que.push(first);
int max = first;
while (!que.empty()) {
int cur = que.front();
que.pop();
visit[cur] = 1;
max = max > cur ? max : cur;
for (int next : graph[cur]) {
if (visit[next] == 0) {
que.push(next);
}
}
}
return max;
}
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> graph(n + 1);
vector<int> max(n + 1, 0); // 初始化 max 数组
vector<int> visit(n + 1, 0); // 初始化访问标记数组
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
}
for (int i = 1; i <= n; i++) { // 对每个节点调用 dfs
if (!visit[i]) {
max[i] = bfs(i, graph, n);
}
}
for (int i = 1; i <= n; i++) {
cout << max[i] << ' '; // 输出每个节点的最大可达节点编号
}
cout << endl;
return 0;
}
反向建图+BFS算法思想:
思路的核心在于理解强连通图的性质,当我反向建图后,我从一个很大的点 x 出发,那么能够到达的其他点都是“本来可以抵达点 x 的点”。这样我们就有A[x能到达的其他点]=x。
- 初始化:创建一个图的邻接表表示,以及两个数组
A
和A_visit
。A
用于存储每个点能够到达的最大编号点,A_visit
用于标记每个点 i 是否已经被计算过A[i],
这样可以减少不必要的耗时。 - 逆序遍历:从编号最大的点开始,逆序遍历每个点。这样做的目的是为了确保在搜索过程中,如果一个点能够通过多个路径到达不同的最大编号点,我们总是先考虑编号最大的路径。
- 广度优先搜索:对于每个
A_visit[i]==0
的点,使用广度优先搜索从该点开始搜索,直到找到能够到达的最大编号点。在搜索过程中,记录每个点能够到达的最大编号点。 - 输出结果:最后,输出数组
A
的内容,即为每个点能够到达的最大编号点。
#include<iostream> // 引入输入输出流库
using namespace std; // 使用标准命名空间
#include<vector> // 引入向量库
#include<queue> // 引入队列库
// 定义BFS函数,用于从指定起点开始搜索能够到达的最大编号点
void bfs(vector<vector<int>>& graph, int start, int n,
vector<int>& A, vector<int>& A_visit) {
vector<bool>visit(n + 1, false); // 定义访问标记数组,初始化为未访问
queue<int>que; // 定义队列,用于BFS
que.push(start); // 将起点加入队列
while (!que.empty()) { // 当队列不为空时循环
int cur = que.front(); // 获取队列的第一个元素
que.pop(); // 将该元素从队列中移除
A[cur] = start; // 记录当前点能够到达的最大编号点为起点
A_visit[cur] = 1; // 标记当前点已访问
visit[cur] = true; // 更新访问标记数组
for (int point : graph[cur]) { // 遍历当前点的所有邻接点
if (visit[point] == false && A_visit[point] == 0) que.push(point); // 如果邻接点未被访问并且没有计算过A[point],则加入队列
}
}
return; // 返回
}
// 定义函数,用于打印数组A的内容
void printA(vector<int>A) {
for (int i = 1; i < A.size(); i++) { // 遍历数组A
cout << A[i] << " "; // 打印每个元素
}
return; // 返回
}
int main() {
int n, m; // 定义点数和边数
cin >> n >> m; // 读取点数和边数
vector<vector<int>>graph(n + 1); // 定义图的邻接表
vector<int>A_visit(n + 1, 0); // 定义访问标记数组,用于记录点i是否被计算过A[i]
vector<int>A(n + 1); // 定义数组A,用于存储每个点能够到达的最大编号点
for (int i = 0; i < m; i++) { // 遍历每条边
int ui, vi; // 定义边的两个端点
cin >> ui >> vi; // 读取边的两个端点
graph[vi].push_back(ui); // 在邻接表中添加边,注意这里是反向添加,因为题目要求从每个点出发
}
for (int i = 1; i <= n; i++) { // 初始化数组A
A[i] = i; // 将每个点初始化为能够到达的最大编号点是其自身
}
for (int i = n; i > 0; i--) { // 从最大的点开始,逆序遍历每个点
if (A_visit[i] == 0) // 如果该点未被访问过
bfs(graph, i, n, A, A_visit); // 从该点开始进行BFS
}
printA(A); // 打印数组A的内容
return 0; // 返回0,表示程序正常结束
}
今天的博客就写到这里了,与诸君共勉之!