梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
AOV 网(Activity On Vertex,顶点表示活动的网)和 AOE 网(Activity On Edge,边表示活动的网)是带权有向无环图(DAG)的两大核心应用:
两者均要求图为有向无环图(DAG),若存在环则无拓扑排序(活动间循环依赖),也无关键路径(项目无法完成)。
图结构的实现方式:
学习经典应用场景前,请根据上面的教程封装好自定义图,所有场景实例直接复用
AOV 网解决“先做什么,后做什么”(顺序问题)
对 AOV 网的顶点进行线性排序,满足:若图中存在有向边,则排序中u一定在v之前。
最易理解、易实现的拓扑排序算法,核心是反复删除入度为 0 的顶点,并更新其邻接顶点的入度,步骤如下:
#include <iostream>
#include <vector>
#include <queue>
#include"LinkedGraph.h"
using namespace std;
// 打印拓扑序列(支持顶点名称映射)
void printKahnSort(const LinkedGraph& adj, vector<int>& topoOrder) {
int n = topoOrder.size();
if (n != adj.vertexNum) {
cout << "无有效拓扑序列!" << endl;
return;
}
cout << "Kahn算法求拓扑序列:";
for(int i=0;i < n;i++)
{
cout << adj.adjList[topoOrder[i]].data;
if(i != n-1)
{
cout << "->";
}
}
cout << endl;
}
// 拓扑排序:Kahn算法
// adj-邻接表,topoOrder-输出拓扑序列
// 返回值:true-排序成功(无环),false-排序失败(有环)
bool kahnSort(const LinkedGraph& adj, vector<int>& topoOrder) {
int n = adj.vertexNum;// 顶点数
vector<int> inDegree(n, 0);// 入度数组
queue<int> q;
// 计算所有顶点的入度,存入入度数组inDegree[];
for (int u = 0; u < n; ++u) {
EdgeNode* edge = adj.adjList[u].firstedge;
while(edge != nullptr)
{
inDegree[edge->adjvex]++;
edge = edge->next;
}
delete edge;
}
// 初始化队列,将所有入度为 0的顶点入队(初始可执行的活动);
for (int i = 0; i < adj.vertexNum; ++i) {
if (inDegree[i] == 0) {
q.push(i);
}
}
// 迭代处理:
while (!q.empty()) {
int u = q.front();
q.pop();
topoOrder.push_back(u); // 出队顶点u,将其加入topoOrder[];
// 遍历u的所有邻接顶点,更新入度
EdgeNode* edge = adj.adjList[u].firstedge;
while(edge != nullptr)
{
int v = edge->adjvex;
inDegree[v]--;// 将v的入度减 1(u完成,v的一个前驱依赖消除);
if (inDegree[v] == 0) {
q.push(v);// 若v的入度变为 0,将v入队(v的所有前驱完成,可执行)。
}
edge = edge->next;
}
delete edge;
}
// 拓扑序列长度等于顶点数,说明无环,排序成功
return topoOrder.size() == n;
}
// 测试案例
int main() {
LinkedGraph graph;
vector<int> topoOrder;// 拓扑序列
// 1. 添加顶点(A、B、C、D、E)
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');
// 2. 添加边(无权有向图)
graph.addDirectedEdge('A','B',1);
graph.addDirectedEdge('A','C',1);
graph.addDirectedEdge('A','D',1);
graph.addDirectedEdge('B','E',1);
graph.addDirectedEdge('C','E',1);
graph.addDirectedEdge('D','E',1);
// 3. 打印邻接矩阵
graph.printAdjacency();
// 4. 求拓扑序列
kahnSort(graph, topoOrder);
// 5. 打印拓扑序列
printKahnSort(graph, topoOrder);
return 0;
}
===== 邻接表(带权)=====
A -> (D, 1) (C, 1) (B, 1)
B -> (E, 1)
C -> (E, 1)
D -> (E, 1)
E ->
Kahn算法求拓扑序列:A->D->C->B->E
AOE 网解决“最快多久完成,哪些活动不能延迟”(时间优化问题);
AOE 网是带权有向无环图(DAG),是 AOV 网的扩展(关注活动时间和项目工期),核心概念:
设顶点数为n,顶点编号0∼n−1,源点为0,汇点为n−1,定义 4 个核心数组(基础版基于邻接表实现):
关键路径依赖拓扑排序(保证正向推导ve[]时,前驱顶点已处理)和逆拓扑排序(保证反向推导vl[]时,后继顶点已处理),核心分 3 步:
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include"LinkedGraph.h"
using namespace std;
bool isBuilt; // 标记图是否已构建
int source; // 源点(入度0)
int sink; // 汇点(出度0)
int vertexNum; // 顶点数(事件数)
// 检查AOE网合法性:必须是DAG、仅有一个源点、仅有一个汇点
bool checkValid(const LinkedGraph& adj, const vector<int> inDegree, const vector<int>& topoOrder) {
if (topoOrder.empty()) {
cerr << "错误:AOE网存在环,不合法!" << endl;
return false;
}
// 找源点(入度0)
vector<int> sourceList;
for (int i = 0; i < vertexNum; i++) {
if (inDegree[i] == 0) sourceList.push_back(i);
}
// 找汇点(出度0)
vector<int> sinkList;
for (int i = 0; i < vertexNum; i++) {
if (adj.adjList[i].firstedge == nullptr) sinkList.push_back(i);
}
if (sourceList.size() != 1) {
cerr << "错误:AOE网需仅有一个源点,当前找到" << sourceList.size() << "个!" << endl;
return false;
}
if (sinkList.size() != 1) {
cerr << "错误:AOE网需仅有一个汇点,当前找到" << sinkList.size() << "个!" << endl;
return false;
}
source = sourceList[0];
sink = sinkList[0];
return true;
}
// 查找从start到end的所有路径
void dfsFindPath(const vector<vector<int>>& adj, int start, int end,
vector<int>& path, vector<vector<int>>& res) {
path.push_back(start);
if (start == end) {
res.push_back(path);
path.pop_back();
return;
}
for (int v : adj[start]) {
dfsFindPath(adj, v, end, path, res);
}
path.pop_back();
}
// 打印关键路径(由关键活动组成,从源点到汇点)
void printCriticalPath(const vector<pair<int, int>>& criticalAct) {
if (criticalAct.empty() || source == -1 || sink == -1) {
cout << "无有效关键路径!" << endl;
return;
}
// 构建关键活动的邻接表
vector<vector<int>> cpAdj(vertexNum);
for (const auto& p : criticalAct) {
cpAdj[p.first].push_back(p.second);
}
// 深度优先搜索(DFS)查找从源点到汇点的关键路径
vector<int> path;
vector<vector<int>> allCriticalPaths;
dfsFindPath(cpAdj, source, sink, path, allCriticalPaths);
// 输出所有关键路径
cout << "AOE网所有关键路径(从源点" << source << "到汇点" << sink << "):" << endl;
for (size_t i = 0; i < allCriticalPaths.size(); i++) {
cout << " 路径" << i+1 << ":";
for (int v : allCriticalPaths[i]) {
cout << adj.adjList[v].data;
if (v != sink) cout << " -> ";
}
cout << endl;
}
}
// 求关键路径:返回关键活动(pair<u,v>),输出工程最短时间、ve、vl、关键活动
vector<pair<int, int>> criticalPath(const LinkedGraph& adj, int& minProjectTime,vector<int>& topoOrder) {
vector<pair<int, int>> criticalActivities; // 存储关键活动<u,v>
vector<int> inDegree(vertexNum, 0);// 入度数组
minProjectTime = 0;
if (!isBuilt || vertexNum == 0) {
cerr << "错误:AOE网未构建或无顶点!" << endl;
return criticalActivities;
}
// 计算所有顶点的入度,存入入度数组inDegree[];
for (int u = 0; u < vertexNum; ++u) {
EdgeNode* edge = adj.adjList[u].firstedge;
while(edge != nullptr)
{
inDegree[edge->adjvex]++;
edge = edge->next;
}
delete edge;
}
// 步骤1:检查AOE网合法性
if (!checkValid(adj, inDegree, topoOrder)) {
return criticalActivities;
}
// 步骤2:求事件最早发生时间ve[]
vector<int> ve(vertexNum, INT_MIN);
ve[source] = 0; // 源点最早发生时间为0
for (int u : topoOrder) { // 按拓扑顺序遍历
EdgeNode* edge = adj.adjList[u].firstedge;
while(edge != nullptr)
{
int v = edge->adjvex;
int w = edge->weight;
if (ve[v] < ve[u] + w) {
ve[v] = ve[u] + w;
}
edge = edge->next;
}
delete edge;
}
minProjectTime = ve[sink]; // 汇点的ve就是工程最短完成时间
// 步骤3:求事件最迟发生时间vl[]
vector<int> vl(vertexNum, INT_MAX);
vl[sink] = ve[sink]; // 汇点最迟发生时间=最早发生时间
// 按逆拓扑顺序遍历(反转拓扑序列)
reverse(topoOrder.begin(), topoOrder.end());
for (int u : topoOrder) {
EdgeNode* edge = adj.adjList[u].firstedge;
while(edge != nullptr)
{
int v = edge->adjvex;
int w = edge->weight;
if (vl[u] > vl[v] - w) {
vl[u] = vl[v] - w;
}
edge = edge->next;
}
delete edge;
}
// 步骤4:遍历所有边,计算松驰时间,找关键活动(slack=0)
cout << "\n===== AOE网关键路径分析 =====" << endl;
cout << "事件最早发生时间ve[]:";
for (int x : ve) cout << x << " ";
cout << "\n事件最迟发生时间vl[]:";
for (int x : vl) cout << x << " ";
cout << "\n工程最短完成时间:" << minProjectTime << endl;
cout << "所有活动分析(u->v, 时间w, 最早开始时间, 最迟开始时间, 松驰时间):" << endl;
for (int u = 0; u < vertexNum; u++) {
EdgeNode* edge = adj.adjList[u].firstedge;
while(edge != nullptr)
{
int v = edge->adjvex;
int w = edge->weight;
int e_activity = ve[u]; // 活动最早开始时间
int l_activity = vl[v] - w; // 活动最迟开始时间
int slack = l_activity - e_activity; // 松驰时间
cout << " " << adj.adjList[u].data << "->" << adj.adjList[v].data << ", " << w << ", "
<< e_activity << ", " << l_activity << ", " << slack << endl;
// 松驰时间为0,是关键活动
if (slack == 0) {
criticalActivities.emplace_back(u, v);
}
edge = edge->next;
}
delete edge;
}
// 输出关键活动
if (!criticalActivities.empty()) {
cout << "关键活动(slack=0):";
for (size_t i = 0; i < criticalActivities.size(); i++) {
int u = criticalActivities[i].first;
int v = criticalActivities[i].second;
cout << adj.adjList[u].data << "->" << adj.adjList[v].data;
if (i != criticalActivities.size() - 1) cout << ", ";
}
cout << endl;
} else {
cerr << "错误:未找到关键活动!" << endl;
}
return criticalActivities;
}
// 测试案例
int main() {
LinkedGraph graph;
vector<int> topoOrder;//拓扑序列
int minProjectTime=0;
// 1. 添加顶点(A、B、C、D、E、F、G、H)
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');
graph.addVertex('F');
graph.addVertex('G');
graph.addVertex('H');
vertexNum = graph.vertexNum;
vector<int> inDegree(vertexNum, 0);// 入度数组
// 2. 添加边(带权有向图)
graph.addDirectedEdge('A','B',7);
graph.addDirectedEdge('A','C',6);
graph.addDirectedEdge('B','E',4);
graph.addDirectedEdge('C','D',3);
graph.addDirectedEdge('C','E',5);
graph.addDirectedEdge('D','F',2);
graph.addDirectedEdge('D','H',5);
graph.addDirectedEdge('E','F',3);
graph.addDirectedEdge('E','G',4);
graph.addDirectedEdge('F','H',2);
graph.addDirectedEdge('G','H',4);
isBuilt = true;
// 3. 打印邻接矩阵
graph.printAdjacency();
// 4. 求拓扑序列
kahnSort(graph, inDegree, topoOrder);
// 5. 打印拓扑序列
printKahnSort(graph, topoOrder);
// 6. 求关键路径
vector<pair<int, int>> criticalAct = criticalPath(graph, minProjectTime, topoOrder);
// 7. 打印关键路径
printCriticalPath(graph, criticalAct);
return 0;
}
===== 邻接表(带权)=====
A -> (C, 6) (B, 7)
B -> (E, 4)
C -> (E, 5) (D, 3)
D -> (H, 5) (F, 2)
E -> (G, 4) (F, 3)
F -> (H, 2)
G -> (H, 4)
H ->
Kahn算法求拓扑序列:A->C->B->D->E->G->F->H
===== AOE网关键路径分析 =====
事件最早发生时间ve[]:0 7 6 9 11 14 15 19
事件最迟发生时间vl[]:0 7 6 14 11 17 15 19
工程最短完成时间:19
所有活动分析(u->v, 时间w, 最早开始时间, 最迟开始时间, 松驰时间):
A->C, 6, 0, 0, 0
A->B, 7, 0, 0, 0
B->E, 4, 7, 7, 0
C->E, 5, 6, 6, 0
C->D, 3, 6, 11, 5
D->H, 5, 9, 14, 5
D->F, 2, 9, 15, 6
E->G, 4, 11, 11, 0
E->F, 3, 11, 14, 3
F->H, 2, 14, 17, 3
G->H, 4, 15, 15, 0
关键活动(slack=0):A->C, A->B, B->E, C->E, E->G, G->H
AOE网所有关键路径(从源点0到汇点7):
路径1:A -> C -> E -> G -> H
路径2:A -> B -> E -> G -> H
AOV 网的典型应用
AOE 网的典型应用