2024.12.18做题,对AI的提问记录,拓扑排序和图的关键活动

什么是拓扑排序

拓扑排序(Topological Sorting)是一种用于有向无环图(DAG, Directed Acyclic Graph)的线性排序。对于图中的每一个有向边 u -> v,顶点 u 在排序中都出现在顶点 v 之前。拓扑排序可以用于解决依赖关系的问题,例如任务调度、编译顺序等。

拓扑排序的常见算法有两种:

  1. Kahn's Algorithm:基于入度的算法。
  2. 深度优先搜索(DFS):基于递归的算法。

以下是使用 Kahn's Algorithm 实现拓扑排序的示例代码:

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

vector<int> topologicalSort(int V, vector<vector<int>>& adj) {
    vector<int> in_degree(V, 0);
    for (int u = 0; u < V; u++) {
        for (int v : adj[u]) {
            in_degree[v]++;
        }
    }

    queue<int> q;
    for (int i = 0; i < V; i++) {
        if (in_degree[i] == 0) {
            q.push(i);
        }
    }

    vector<int> topo_order;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        topo_order.push_back(u);

        for (int v : adj[u]) {
            if (--in_degree[v] == 0) {
                q.push(v);
            }
        }
    }

    if (topo_order.size() != V) {
        throw runtime_error("Graph is not a DAG");
    }

    return topo_order;
}

int main() {
    int V = 8;
    vector<vector<int>> adj(V);
    adj[0] = {2, 4};
    adj[1] = {2, 4};
    adj[3] = {4};
    adj[4] = {5, 6};
    adj[5] = {7};

    try {
        vector<int> result = topologicalSort(V, adj);
        for (int v : result) {
            cout << "C" << v << " ";
        }
        cout << endl;
    } catch (const exception& e) {
        cout << e.what() << endl;
    }

    return 0;
}

这个示例代码实现了 Kahn's Algorithm 来进行拓扑排序,并输出排序结果。如果图中存在环,则会抛出异常。

Kahn's Algorithm的过程是什么

Kahn's Algorithm 是一种基于入度的拓扑排序算法。其过程如下:

  1. 计算每个节点的入度
    • 遍历图中的所有边,对于每条边 (u, v),增加节点

v

的入度。

  1. 初始化队列
    • 创建一个队列

q

,将所有入度为 0 的节点入队。

  1. 处理队列中的节点
    • 当队列不为空时,执行以下步骤:
      • 从队列中取出一个节点

u

,将其添加到拓扑排序结果

topo_order

中。
- 遍历节点

u

的所有邻接节点

v

,将这些节点的入度减 1。如果某个邻接节点

v

的入度变为 0,则将其入队。

  1. 检查是否存在环
    • 如果拓扑排序结果中的节点数量不等于图中的节点数量,则说明图中存在环,无法进行拓扑排序。

以下是 Kahn's Algorithm 的代码实现:

#include <iostream>
#include <vector>
#include <queue>
#include <stdexcept>

using namespace std;

vector<int> topologicalSort(int V, vector<vector<int>>& adj) {
    vector<int> in_degree(V, 0);
    for (int u = 0; u < V; u++) {
        for (int v : adj[u]) {
            in_degree[v]++;
        }
    }

    queue<int> q;
    for (int i = 0; i < V; i++) {
        if (in_degree[i] == 0) {
            q.push(i);
        }
    }

    vector<int> topo_order;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        topo_order.push_back(u);

        for (int v : adj[u]) {
            if (--in_degree[v] == 0) {
                q.push(v);
            }
        }
    }

    if (topo_order.size() != V) {
        throw runtime_error("Graph is not a DAG");
    }

    return topo_order;
}

int main() {
    int V = 8;
    vector<vector<int>> adj(V);
    adj[0] = {2, 4};
    adj[1] = {2, 4};
    adj[3] = {4};
    adj[4] = {5, 6};
    adj[5] = {7};

    try {
        vector<int> result = topologicalSort(V, adj);
        for (int v : result) {
            cout << "C" << v << " ";
        }
        cout << endl;
    } catch (const exception& e) {
        cout << e.what() << endl;
    }

    return 0;
}

这个代码实现了 Kahn's Algorithm,并输出拓扑排序结果。如果图中存在环,则会抛出异常。

vim中如何全选

在 Vim 中全选文本,可以使用以下步骤:

  1. gg 移动到文件的开头。
  2. V进入可视模式。
  3. G 选择到文件的末尾。

这样就可以全选整个文件的内容。
即ggVG

queue.pop()有返回值吗

queue.pop()

在 C++ 标准库中没有返回值。它只是移除队列中的第一个元素。如果你需要获取并移除队列中的第一个元素,可以先使用

queue.front()

获取元素,然后再调用

queue.pop()

移除它。

例如:

int u = q.front();
q.pop();

算法8-5 拓扑排序 的 提交

/*
算法8-5 拓扑排序
请编写程序,实现对有向无权图中的顶点进行拓扑排序的算法。
注意:如果拓扑序不唯一,输出任何一个序列都可以,由特殊裁判程序判定正确性。

输入格式:
输入首先在第一行给出两个正整数,依次为当前要创建的图的顶点数 n(≤100)和边数 m。
随后一行顺序给出 n 个顶点对应的字符串,由不超过 3 个英文字母或数字组成。
接下来 m 行,每行给出一条有向边的起点编号、终点编号。顶点编号从 0 开始。
同行数字和字符串均以一个空格分隔。

输出格式:
参考样例。
首先在一行中输出 该图拓扑序存在性为  x,其中 x 为 1 表示该图顶点有拓扑序,
为 0 表示没有。
随后在一行中按顶点拓扑序存输出每个顶点对应的字符串。
为输出简单起见,每个字符串后有一个空格。
注意:如果拓扑序不存在,最后一行可以输出任何字符,均判为正确。

输入样例 1:
8 8
C0 C1 C2 C3 C4 C5 C6 C7
0 2
0 4
1 2
1 4
3 4
4 5
4 6
5 7
输出样例 1:
该图拓扑序存在性为 1
C0 C1 C3 C2 C4 C6 C5 C7 

输入样例 2:
8 9
C0 C1 C2 C3 C4 C5 C6 C7
0 2
0 4
1 2
1 4
3 4
4 5
4 6
5 7
7 4
输出样例 2:
该图拓扑序存在性为 0
这里输出什么都不重要
*/
#include <iostream>
#include <vector>
#include <string>
#include <queue>

int main(){
    int n,m;
    std::cin>>n>>m;
    std::vector<std::string> vertexName(n);
    for(int i = 0;i<n;i++){
        std::cin>>vertexName[i];
    }

    std::vector<std::vector<int>> graph(n);
    for(int i = 0;i<m;i++){
        int start,end;
        std::cin>>start>>end;
        graph[start].push_back(end);
    }

    std::queue<int> zeroInDegree;
    std::vector<int> inDegree(n,0);
    std::vector<std::string> result;
    
    for(int i = 0;i<n;i++){
        for(int j : graph[i]){
            //i -> j
            inDegree[j]++;//inDegree[j]表示指向j的边的数量
        }
    }

    for(int i = 0;i<n;i++){
        if(inDegree[i] == 0){
            zeroInDegree.push(i);
        }
    }
    while(!zeroInDegree.empty()){
        int u = zeroInDegree.front();
        zeroInDegree.pop();
        result.push_back(vertexName[u]);
        for(int end : graph[u]){
            if(--inDegree[end]==0){
                zeroInDegree.push(end);
            }
        }
    }
    if(result.size()!=n){
        std::cout<< "该图拓扑序存在性为 0" <<std::endl;
        std::cout<<"aaa"<<std::endl;
    }else{
        std::cout<<"该图拓扑序存在性为 1"<<std::endl;
        for(int i = 0;i<n-1;i++){
            std::cout<<result[i]<<' ';
        }
        std::cout<<result[n-1]<<std::endl;
    }
    return 0;
}

什么是图的关键活动

图的关键活动是指在带权有向图中,位于关键路径上的活动。关键路径是从起点到终点的最长路径,路径上的每个活动(边)都是关键活动。如果任何一个关键活动被延迟,整个项目的完成时间也会被延迟。

关键活动的特点:

  1. 关键活动位于关键路径上。
  2. 关键活动的最早开始时间和最晚开始时间相同。
  3. 关键活动的最早结束时间和最晚结束时间相同。

在求解关键活动时,通常需要以下步骤:

  1. 拓扑排序:对图进行拓扑排序,确保处理顺序。
  2. 计算最早开始时间和最早结束时间:从起点开始,逐步计算每个节点的最早开始时间和最早结束时间。
  3. 计算最晚开始时间和最晚结束时间:从终点开始,逐步计算每个节点的最晚开始时间和最晚结束时间。
  4. 确定关键活动:比较每个活动的最早开始时间和最晚开始时间,如果相同,则该活动是关键活动。

以下是一个简单的示例代码,展示如何求解图中的关键活动:

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

struct Edge {
    int u, v, weight;
};

void findCriticalActivities(int n, int m, vector<Edge>& edges) {
    vector<vector<pair<int, int>>> adj(n);
    vector<int> in_degree(n, 0);
    vector<int> earliest(n, 0);
    vector<int> latest(n, INT_MAX);

    for (const auto& edge : edges) {
        adj[edge.u].emplace_back(edge.v, edge.weight);
        in_degree[edge.v]++;
    }

    queue<int> q;
    for (int i = 0; i < n; ++i) {
        if (in_degree[i] == 0) {
            q.push(i);
        }
    }

    vector<int> topo_order;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        topo_order.push_back(u);

        for (const auto& [v, weight] : adj[u]) {
            earliest[v] = max(earliest[v], earliest[u] + weight);
            if (--in_degree[v] == 0) {
                q.push(v);
            }
        }
    }

    latest[n - 1] = earliest[n - 1];
    for (int i = n - 2; i >= 0; --i) {
        int u = topo_order[i];
        for (const auto& [v, weight] : adj[u]) {
            latest[u] = min(latest[u], latest[v] - weight);
        }
    }

    vector<pair<int, int>> critical_activities;
    for (const auto& edge : edges) {
        if (earliest[edge.u] == latest[edge.u] && earliest[edge.u] + edge.weight == latest[edge.v]) {
            critical_activities.emplace_back(edge.u, edge.v);
        }
    }

    sort(critical_activities.begin(), critical_activities.end());

    for (const auto& [u, v] : critical_activities) {
        cout << "<" << u << ", " << v << ">" << endl;
    }

    cout << "关键路径分析结果为 " << (critical_activities.empty() ? 0 : 1) << endl;
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<Edge> edges(m);
    for (int i = 0; i < m; ++i) {
        cin >> edges[i].u >> edges[i].v >> edges[i].weight;
    }

    findCriticalActivities(n, m, edges);

    return 0;
}

这个代码实现了求解图中关键活动的算法,并输出关键活动和关键路径分析结果。

adj是什么的缩写

adj 通常是 adjacency(邻接)的缩写,用于表示图的邻接表或邻接矩阵。在图论中,邻接表是一种常用的数据结构,用于存储图中各个顶点及其相邻的顶点。

活动是指什么

在图论或项目管理的上下文中,活动通常指的是图中的边(边缘),代表需要完成的任务步骤。每个活动连接两个顶点,表示任务的开始和结束点,并且通常带有一个权重,该权重可以代表任务的持续时间成本或其他相关的度量

具体来说:

  1. 图中的活动

    • 顶点(节点):表示任务的里程碑或阶段。
    • 边(活动):表示从一个任务到另一个任务的依赖关系,边上的权重通常表示完成该任务所需的时间或资源。
  2. 关键活动

    • 在关键路径法(Critical Path Method, CPM)中,关键活动是指那些位于关键路径上的活动。关键路径是项目中最长的路径,决定了项目的最短完成时间。
    • 关键活动的延迟将直接导致整个项目的延迟,因为它们没有任何浮动时间(即最早开始时间和最晚开始时间相同)。

示例
假设有一个项目包含以下活动:

A -> B (时间: 4)
A -> C (时间: 3)
B -> D (时间: 2)
C -> D (时间: 5)
  • 活动 A -> BA -> C 分别表示从任务 A 到任务 BC 的两个不同路径。
  • 计算关键路径时,会发现 A -> B -> D 的总时间为 6,而 A -> C -> D 的总时间为 8。因此,A -> CC -> D 是关键活动,因为它们位于最长路径上。

总结

  • 活动在图中通常表示任务步骤,由图的来表示。
  • 每个活动有一个权重,表示完成该活动所需的时间或资源。
  • 关键活动是位于关键路径上的活动,决定了整个项目的完成时间。

什么是图的关键活动,如何求带权的有向图中的关键活动

图的关键活动是指在项目管理中,位于关键路径上的活动。关键路径是指从项目开始到结束所需的最长路径,关键路径上的活动是那些如果延迟了就会导致整个项目延迟的活动。

要在带权的有向图中求关键活动,可以使用拓扑排序和关键路径法。以下是步骤:

  1. 构建图:根据输入的顶点数和边数,构建有向图。
  2. 拓扑排序:对图进行拓扑排序,得到一个拓扑序列。
  3. 计算最早开始时间(Earliest Start Time, EST):根据拓扑序列,从起点开始,计算每个顶点的最早开始时间。
  4. 计算最晚开始时间(Latest Start Time, LST):根据拓扑序列的逆序,从终点开始,计算每个顶点的最晚开始时间。
  5. 确定关键活动:对于每条边 (u, v),如果满足 EST[u] + weight(u, v) == LST[v],则该边是关键活动。

下面是一个示例代码,展示如何实现上述步骤:



#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

struct Edge {
    int from, to, weight;
};

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<Edge>> graph(n);
    vector<int> inDegree(n, 0);
    vector<int> earliest(n, 0);
    vector<int> latest(n, INT_MAX);
    vector<Edge> edges(m);

    for (int i = 0; i < m; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        graph[u].push_back({u, v, w});
        inDegree[v]++;
        edges[i] = {u, v, w};
    }

    // Topological sort
    queue<int> q;
    for (int i = 0; i < n; ++i) {
        if (inDegree[i] == 0) q.push(i);
    }

    vector<int> topoOrder;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        topoOrder.push_back(u);
        for (const auto& edge : graph[u]) {
            int v = edge.to;
            if (--inDegree[v] == 0) q.push(v);
            earliest[v] = max(earliest[v], earliest[u] + edge.weight);
        }
    }

    // Calculate latest start time
    latest[topoOrder.back()] = earliest[topoOrder.back()];
    for (int i = topoOrder.size() - 2; i >= 0; --i) {
        int u = topoOrder[i];
        for (const auto& edge : graph[u]) {
            int v = edge.to;
            latest[u] = min(latest[u], latest[v] - edge.weight);
        }
    }

    // Find critical activities
    vector<pair<int, int>> criticalActivities;
    for (const auto& edge : edges) {
        int u = edge.from, v = edge.to, w = edge.weight;
        if (earliest[u] + w == latest[v]) {
            criticalActivities.push_back({u, v});
        }
    }

    // Sort and output critical activities
    sort(criticalActivities.begin(), criticalActivities.end());
    for (const auto& activity : criticalActivities) {
        cout << "<" << activity.first << ", " << activity.second << ">" << endl;
    }

    // Output result
    cout << "关键路径分析结果为 1" << endl;

    return 0;
}

这个代码实现了求带权有向图中关键活动的算法。首先读取输入,构建图和边列表,然后进行拓扑排序,计算最早和最晚开始时间,最后确定并输出关键活动。

最早开始时间和最晚开始时间如何定义的

最早开始时间(Earliest Start Time, EST)和最晚开始时间(Latest Start Time, LST)是关键路径法中的两个重要概念。

  1. 最早开始时间(EST)

    • 定义:在不延迟前置任务的情况下,某个任务可以开始的最早时间。
    • 计算方法:从起点开始,按照拓扑排序的顺序,逐步计算每个顶点的最早开始时间。对于每个顶点 v,其最早开始时间等于所有指向它的边的起点的最早开始时间加上边的权重的最大值。
  2. 最晚开始时间(LST)

    • 定义:在不延迟整个项目的情况下,某个任务可以开始的最晚时间。
    • 计算方法:从终点开始,按照拓扑排序的逆序,逐步计算每个顶点的最晚开始时间。对于每个顶点

u

,其最晚开始时间等于所有从它出发的边的终点的最晚开始时间减去边的权重的最小值。

下面是如何在代码中计算最早开始时间和最晚开始时间的示例:



#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <climits>

struct Edge {
    int u, v, weight;
};

void findCriticalActivities(int n, int m, std::vector<std::vector<std::pair<int, int>>> adj) {
    std::vector<int> inDegree(n, 0);
    std::vector<int> earliest(n, 0);
    std::vector<int> latest(n, INT_MAX);

    for (int i = 0; i < n; ++i) {
        for (auto pair : adj[i]) {
            inDegree[pair.first]++;
        }
    }

    std::queue<int> zeroInDegree;
    for (int i = 0; i < n; ++i) {
        if (inDegree[i] == 0) {
            zeroInDegree.push(i);
        }
    }

    std::vector<int> topoOrder;
    while (!zeroInDegree.empty()) {
        int u = zeroInDegree.front();
        zeroInDegree.pop();
        topoOrder.push_back(u);
        for (const auto& [end, weight] : adj[u]) {
            if (--inDegree[end] == 0) {
                zeroInDegree.push(end);
            }
            earliest[end] = std::max(earliest[end], earliest[u] + weight);
        }
    }

    latest[topoOrder.back()] = earliest[topoOrder.back()];
    for (int i = topoOrder.size() - 2; i >= 0; --i) {
        int u = topoOrder[i];
        for (const auto& [end, weight] : adj[u]) {
            latest[u] = std::min(latest[u], latest[end] - weight);
        }
    }

    std::vector<std::pair<int, int>> criticalActivities;
    for (int u = 0; u < n; ++u) {
        for (const auto& [v, weight] : adj[u]) {
            if (earliest[u] + weight == latest[v]) {
                criticalActivities.push_back({u, v});
            }
        }
    }

    std::sort(criticalActivities.begin(), criticalActivities.end());
    for (const auto& activity : criticalActivities) {
        std::cout << "<" << activity.first << ", " << activity.second << ">" << std::endl;
    }

    std::cout << "关键路径分析结果为 1" << std::endl;
}

int main() {
    int n, m;
    std::cin >> n >> m;
    std::vector<std::vector<std::pair<int, int>>> adj(n);
    for (int i = 0; i < m; ++i) {
        int u, v, w;
        std::cin >> u >> v >> w;
        adj[u].push_back({v, w});
    }
    findCriticalActivities(n, m, adj);
    return 0;
}

这个代码实现了计算最早开始时间和最晚开始时间的逻辑,并找出了关键活动。

找到具有 1 个许可证类型的类似代码

项目的运作的规则在图中是如何体现的

项目的运作规则在图中通过以下方式体现:

  1. 顶点(节点)

    • 表示活动或事件:每个顶点代表项目中的一个活动或事件。例如,项目的开始和结束也可以作为特殊的顶点。
  2. 边(有向边)

    • 表示活动之间的依赖关系:有向边从一个顶点指向另一个顶点,表示前一个活动是后一个活动的前置条件。
    • 权重表示活动的持续时间:边的权重通常表示完成该活动所需的时间。
  3. 有向无环图(DAG)

    • 项目的活动和依赖关系形成一个有向无环图,确保没有循环依赖,便于进行拓扑排序和关键路径分析。
  4. 关键路径

    • 图中的最长路径决定了项目的最短完成时间。关键路径上的活动是那些如果延迟将导致整个项目延迟的关键活动。
  5. 最早与最晚开始时间

    • 每个顶点有最早开始时间(EST)和最晚开始时间(LST),用于确定活动的时间安排和识别关键活动。

示例图示

顶点0 → 顶点1 (权重5)
顶点0 → 顶点2 (权重3)
顶点1 → 顶点4 (权重4)
顶点1 → 顶点5 (权重2)
...

在这个图中,顶点0是项目的起点,顶点8是终点。边的方向表示活动的先后顺序,权重表示每个活动的持续时间。通过分析这个图,可以确定哪些活动位于关键路径上,从而优化项目管理和时间安排。

最长路径是什么

最长路径是指在有向图中,从一个起点到一个终点,经过的边的权重之和最大的路径。在项目管理中,最长路径通常用于确定项目的关键路径,即项目完成所需的最短时间。计算最长路径有助于识别哪些活动对项目的完成时间具有决定性影响,这些活动即为关键活动。

在有向无环图(DAG)中,可以通过拓扑排序结合动态规划的方法高效地计算最长路径。以下是一个简单的示例代码:



#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <climits>

using namespace std;

struct Edge {
    int from, to, weight;
};

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<Edge>> graph(n);
    vector<int> inDegree(n, 0);
    vector<int> earliest(n, 0);
    vector<Edge> edges(m);

    for(int i = 0; i < m; ++i){
        int u, v, w;
        cin >> u >> v >> w;
        graph[u].push_back({u, v, w});
        inDegree[v]++;
        edges[i] = {u, v, w};
    }

    // Topological sort
    queue<int> q;
    for(int i = 0; i < n; ++i){
        if(inDegree[i] == 0) q.push(i);
    }

    vector<int> topoOrder;
    while(!q.empty()){
        int u = q.front(); q.pop();
        topoOrder.push_back(u);
        for(auto &edge : graph[u]){
            int v = edge.to;
            if(--inDegree[v] == 0) q.push(v);
            earliest[v] = max(earliest[v], earliest[u] + edge.weight);
        }
    }

    // Find the longest path
    int maxTime = 0;
    for(auto time : earliest) {
        maxTime = max(maxTime, time);
    }

    cout << "最长路径的长度为: " << maxTime << endl;

    return 0;
}

这个代码读取一个有向图,进行拓扑排序并计算每个节点的最早开始时间,从而确定最长路径的长度。

我的理解==================================================================

    //一个结点代表一个任务,该节点指向另一个结点的边的权重表示完成该节点到达另一个结点需要的时间
    //一个结点需要它的所有前置结点都完成后才能开始
    //没有前置结点的结点是起点
    //项目开始时间为0,项目开始时,所有起点结点任务开始
    //没有指向的结点是终点
    //从项目开始到达不同终点的时间花费可能不同,其中最长的那个是整个项目的花费时间

    //一个结点的最早开始时间是从所有起点到达该结点所花费的可能时间中最长的那个
    //起点的最早开始时间是0,起点指向的结点的最早开始时间是边的权重

    //当一个结点所有前置结点都完成后,该起点可以开始
    //如果可以开始但是不开始的话,不一定会浪费时间
    //因为有可能该结点指向的某结点的另外的(非该节点)的前置起点完成的时间比该节点的完成时间长
    //最晚开始时间是该节点的可以开始时间加上能等待的最长时间
    //终点的最晚开始时间是项目花费时间
    //花费时间较少的终点可以一直等待,知道花费时间最长的终点的可以开始时间
    //指向终点的结点的最晚开始时间是终点的最晚开始时间减去边的权重
    //结点的最晚开始时间是项目花费时间减去该节点到最晚终点的边的最小权重和

我的最终代码

/*
算法8-6 求图中关键活动
请编写程序,实现求带权的有向图中关键活动的算法。

输入格式:
输入首先在第一行给出两个正整数,依次为当前要创建的图的顶点数 n(≤100)和边数 m。
随后 m 行,每行给出一条有向边的起点编号、终点编号、权重。顶点编号从 0 开始,权重(≤100)为整数。同行数字均以一个空格分隔。

输出格式:
按格式 <u, v> 输出关键活动,其中 u 为起点编号,v 为终点编号。
按起点编号的升序,每行输出一个活动。起点编号相同时,与输入的顺序相反,即先输入的边后输出。
最后一行输出 关键路径分析结果为 x,其中 x 为 1 表示关键路径已成功求出,为 0 表示不成功。

输入样例 1:
9 12
0 1 5
0 2 3
1 4 4
1 5 2
2 3 6
2 4 1
3 6 7
4 6 3
4 7 5
5 7 6
6 8 2
7 8 8
输出样例 1:
<0, 1>
<1, 4>
<4, 7>
<7, 8>
关键路径分析结果为 1
输入样例 2:
8 9
0 2 3
0 4 5
1 2 12
1 4 7
3 4 9
4 5 2
4 6 1
5 7 2
7 4 41
输出样例 2:
关键路径分析结果为 0
*/


#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <cstdint>
#include <climits>
void findCriticalActivities(int n,int m,std::vector<std::vector<std::pair<int,int>>> endWeight){
    //n个顶点, m条边
    //adj[i]为第i个结点
    std::vector<int> inDegree(n,0);//指向每个结点的节点数
    std::vector<int> earlist(n,0);//最早开始时间(EST)
    std::vector<int> lastest(n,INT_MAX);//最晚开始时间(LST)
    int projectTime = 0;
    //一个结点代表一个任务,该节点指向另一个结点的边的权重表示完成该节点到达另一个结点需要的时间
    //一个结点需要它的所有前置结点都完成后才能开始
    //没有前置结点的结点是起点
    //项目开始时间为0,项目开始时,所有起点结点任务开始
    //没有指向的结点是终点
    //从项目开始到达不同终点的时间花费可能不同,其中最长的那个是整个项目的花费时间

    //一个结点的最早开始时间是从所有起点到达该结点所花费的可能时间中最长的那个
    //起点的最早开始时间是0,起点指向的结点的最早开始时间是边的权重

    //当一个结点所有前置结点都完成后,该起点可以开始
    //如果可以开始但是不开始的话,不一定会浪费时间
    //因为有可能该结点指向的某结点的另外的(非该节点)的前置起点完成的时间比该节点的完成时间长
    //最晚开始时间是该节点的可以开始时间加上能等待的最长时间
    //终点的最晚开始时间是项目花费时间
    //花费时间较少的终点可以一直等待,知道花费时间最长的终点的可以开始时间
    //指向终点的结点的最晚开始时间是终点的最晚开始时间减去边的权重
    //结点的最晚开始时间是项目花费时间减去该节点到最晚终点的边的最小权重和
    for(int i = 0;i<n;i++){
        for(auto pair : endWeight[i]){
            inDegree[pair.first]++;
        }
    }
    std::queue<int> zeroInDegree;
    for(int i = 0;i<n;i++){
        if(inDegree[i]==0){
            zeroInDegree.push(i);
        }
    }
    std::vector<int> topoOrder;
    while(!zeroInDegree.empty()){
        int u = zeroInDegree.front();
        zeroInDegree.pop();
        topoOrder.push_back(u);
        for(const auto& [end,weight] : endWeight[u]){
            earlist[end] = std::max(earlist[end],earlist[u]+weight);
            projectTime = std::max(projectTime,earlist[end]);
            //遍历结点u指向的结点,将它们的入度减1
            if(--inDegree[end]==0){
                zeroInDegree.push(end);
            }
        }
    }
    if(topoOrder.size()!=n){
        std::cout<<"关键路径分析结果为 0";
        return;
    }
    lastest[n-1] = projectTime;
    for(int i = n-2;i>=0;i--){
        int u = topoOrder[i];
        for(const auto& [end,weight] : endWeight[u]){
            lastest[u] = std::min(lastest[u],lastest[end]-weight);
        }
    }
    for(int i = 0;i<n;i++){
        int start = topoOrder[i];
        if(endWeight[start].size()>0 ){
            for(auto j = endWeight[start].end()-1;j>=endWeight[start].begin();j--){
                int end = j->first;
                int weight = j->second;
                if(earlist[start]+weight == lastest[end]){
                    std::cout<<'<'<<start<<", "<<end<<'>'<<std::endl;
                }
            }
        }
    }
    std::cout<<"关键路径分析结果为 1";
}
int main(){
    int n,m;
    std::cin>>n>>m;
    std::vector<std::vector<std::pair<int,int>>> endWeight(n);
    for(int i = 0;i<m;i++){
        int start,end,weight;
        std::cin>>start>>end>>weight;
        endWeight[start].push_back({end,weight});
    }
    findCriticalActivities(n,m,endWeight);
    return 0;
}
posted @ 2024-12-18 20:38  Kazuma_124  阅读(12)  评论(0编辑  收藏  举报