【刷题笔记】 AOV网的拓扑排序
有向无环图(DAG)
如果一个有向图不存在环,也就是任意结点都无法通过一些有向边回到自身,那么称这个有向图为有向无环图
AOV 网络
在有向图中,用顶点表示活动,用有向边 <i,j> 表示活动i是活动j的必须条件,这种有向图称为用顶点表示活动的网络(Active on vertices),简称AOV网络。
在AOV网络中,如果活动Vi必须在Vj之前进行,则存在有向边,称Vi为Vj的直接前驱,Vj为Vi的直接后继,这种前驱与后继的关系具有传递性和反自反性(AOV网络中不能出现回路,有向环)。
检测有向环->拓扑排序
如果拓扑排序能够将AOV网络的所有顶点都排入一个拓扑有序的序列中,则说明该AOV网络中没有有向环。
⭐算法描述
- 拓扑排序的关键是反复找到并移除入度为 0 的顶点(即没有前驱依赖的活动),并更新其邻接顶点的入度,直到所有顶点都被处理或发现环(此时无法完成拓扑排序)。
给出 n 个任务,每两个任务之间有相互依赖关系,比如 A 任务一定要在 B 任务之前完成才行,问是否可以完成所有任务。这就是标准的 AOV 网的拓扑排序问题。
拓扑排序问题的解决办法主要是循环执行以下两步,直到不存在入度为0的顶点为止:
- 选择一个入度为0的顶点并输出;
- 从网中删除此顶点及所有出边。
循环结束后,若输出的顶点数小于网中的顶点数,则输出“有回路”信息,即无法完成所有任务;否则输出的顶点序列就是一种拓扑序列,即可以完成所有任务。
具体步骤:
1. 初始化入度数组:计算所有顶点的入度(指向该顶点的边的数量)。
2. 初始化队列:将所有入度为 0 的顶点加入队列(这些顶点没有前驱,可以优先执行)。
3. 处理队列中的顶点:
- 从队列中取出一个顶点 u,将其加入拓扑排序结果列表。
- 遍历 u 的所有邻接顶点 v,将 v 的入度减 1(因为 u 已被处理,依赖关系解除)。
- 若 v 的入度减为 0,将其加入队列。
4. 检查结果:
- 若拓扑排序结果包含所有顶点,则排序成功。
- 否则,图中存在环,无法进行拓扑排序。
代码示例
#include <iostream>
#include <vector>
#include <queue>
using namespace std;// 拓扑排序函数
// 参数:n为顶点数,adj为邻接表,返回拓扑排序结果(空表示存在环)
vector<int> topologicalSort(int n, const vector<vector<int>>& adj) {vector<int> inDegree(n, 0); // 入度数组// 计算每个顶点的入度for (int u = 0; u < n; ++u) {for (int v : adj[u]) {inDegree[v]++;}}queue<int> q; // 存储入度为0的顶点for (int u = 0; u < n; ++u) {if (inDegree[u] == 0) {q.push(u);}}vector<int> result; // 拓扑排序结果while (!q.empty()) {int u = q.front();q.pop();result.push_back(u);// 处理u的邻接顶点,更新入度for (int v : adj[u]) {inDegree[v]--;if (inDegree[v] == 0) {q.push(v);}}}// 若结果包含所有顶点,则排序成功;否则存在环if (result.size() != n) {return {}; // 空列表表示有环}return result;
}int main() {// 示例:AOV网(顶点0~3)// 边:0->1, 0->2, 1->3, 2->3int n = 4;vector<vector<int>> adj = {{1, 2}, // 0的邻接顶点{3}, // 1的邻接顶点{3}, // 2的邻接顶点{} // 3的邻接顶点};vector<int> res = topologicalSort(n, adj);if (res.empty()) {cout << "存在环,无法拓扑排序" << endl;} else {cout << "拓扑排序结果:";for (int u : res) {cout << u << " ";}// 可能的输出:0 1 2 3 或 0 2 1 3(拓扑排序结果不唯一)}return 0;
}