拓扑排序、关键路径(AOV、AOE网)

news2025/3/12 17:52:46

拓扑排序(AOV网)

相关知识

在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,一个工程常被分为多个小的子工程,这些子工程被称为活动(Activity)。 在有向图中若以顶点表示活动,有向边表示活动之间的先后关系,这样的图简称为AOV网。 图中的拓扑排序算法(Topological Sort)可以给出一个活动的合法序列。

拓扑序列  设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列v1, v2, …, vn称为一个拓扑序列,当且仅当满足下列条件:若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前。

拓扑排序  对一个有向图构造拓扑序列的过程称为拓扑排序 。

图的存储结构

采用邻接表存储 ,在顶点表中增加一个入度域 in。

栈S:存储所有无前驱的顶点。

拓扑排序算法——伪代码

  1. 栈S初始化;累加器count初始化;
  2. 扫描顶点表,将没有前驱的顶点压栈;
  3. 当栈S非空时循环 3.1 vj=退出栈顶元素;输出vj;累加器加1; 3.2 将顶点vj的各个邻接点的入度减1; 3.3 将新的入度为0的顶点入栈;
  4. if (count<vertexNum) 输出有回路信息;

输入输出

第一行输入顶点个数和边的个数 后续行输入边依附的两个顶点的数字编号(测试用例的编号都从0开始) 输出拓扑排序的结果。如果存在环,输出拓扑排序的部分结果,再输出“图中存在环,无法拓扑排序”。

测试数据1

拓扑排序测试用例1

输入

7 10

0 2

0 3

1 3

1 5

2 4

3 4

3 5

3 6

4 6

5 6

输出

1 0 2 3 4 5 6

测试数据2

拓扑排序测试用例2

输入

6 8

0 2

0 3

1 3

1 5

2 4

3 4

4 5

5 3

输出

1 0 2 图中存在环,无法拓扑排序

代码

#include<iostream>
#include<stack>
#include<cstring>
#define MAX 100
using namespace std;
typedef struct ArcNode          //边结点
{
    int adjvex;                 //顶点下标
    ArcNode *next;
} ArcNode;
typedef struct
{
    int in;                     //in是入度
    int vertex;              //顶点信息
    ArcNode *firstEdge;
} vertexNode,VertexNode[MAX];

class ALGraph
{
private:
    int vertexNum,arcNum;   //顶点数,边数
    VertexNode adjList;     //顶点数组
    stack<vertexNode> s;    //栈
    int count=0;            //计数
public:
    ALGraph(int v[],int n,int e);
    void TopologicalSort(); //拓扑排序
};
void ALGraph::TopologicalSort()
{
    for(int i=0; i<vertexNum; i++)  //in为0则压栈
    {
        if(adjList[i].in==0)
        {
            s.push(adjList[i]);
        }
    }
    while(!s.empty())               //循环终止条件:栈为空
    {
        vertexNode v=s.top();       //弹栈输出
        s.pop();
        cout<<v.vertex<<" ";
        count++;                    //计数加一
        ArcNode *a=v.firstEdge;
        while(a)                    //对弹出的结点遍历,所有遍历过的结点的in-1
        {
            adjList[a->adjvex].in--;
            int tmp=adjList[a->adjvex].in;
            if(tmp==0)              //如果某结点的in变为0,则将其压栈
            {
                s.push(adjList[a->adjvex])    ;
            }
            a=a->next;
        }
    }
    if(count<vertexNum)
    cout<<"图中存在环,无法拓扑排序";//如果计数小于顶点数则说明有环
}
ALGraph::ALGraph(int v[],int n,int e)    //构造函数
{
    vertexNum=n;
    arcNum=e;
    for(int i=0; i<vertexNum; i++)          //顶点初始化
    {
        adjList[i].in=0;
        adjList[i].vertex=v[i];
        adjList[i].firstEdge=NULL;
    }
    ArcNode *s;
    int vi,vj;
    for(int i=0; i<arcNum; i++)
    {
        s=new ArcNode;
        cin>>vi>>vj;
        s->adjvex=vj;
        s->next=adjList[vi].firstEdge;      //头插法
        adjList[vi].firstEdge=s;
        adjList[vj].in++;                   //入度加一
    }
}
int main()
{
    int n,e;
    cin>>n>>e;
    int v[MAX];
    for(int i=0; i<n; i++)
    {
        v[i]=i;
    }
    ALGraph algraph(v,n,e);
    algraph.TopologicalSort();
    return 0;
}

关键路径(AOE网)

AOE网

在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,边上的权值表示活动的持续时间,称这样的有向图叫做边表示活动的网,简称AOE网。AOE网中没有入边的顶点称为始点(或源点),没有出边的顶点称为终点(或汇点)。

AOE网的性质

⑴ 只有在某顶点所代表的事件发生后,从该顶点出发的各活动才能开始; ⑵ 只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。

关键路径

在AOE网中,从始点到终点具有最大路径长度(该路径上的各个活动所持续的时间之和)的路径称为关键路径。

关键活动

关键路径上的活动称为关键活动。

关键路径可能不只一条,重要的是找到关键活动

关键活动的计算方法

首先计算以下与关键活动有关的量:

⑴ 事件的最早发生时间ve[k]

⑵ 事件的最迟发生时间vl[k]

⑶ 活动的最早开始时间ee[i]

⑷ 活动的最晚开始时间el[i]

最后计算各个活动的时间余量 el[k] - ee[k],时间余量为0者即为关键活动。

关键路径算法

⑴ 事件的最早发生时间ve[k] ve[前驱结点]+边 最大的一组,顺着求

⑵ 事件的最迟发生时间vl[k] vl[后继结点]-边 最小的一组, 逆着求

⑶ 活动的最早开始时间ee[i] 边的起点的ve

⑷ 活动的最晚开始时间el[i] 边的终点的vl-边长

关键路径算法伪码

step1、求ve

调用拓扑排序函数,创建数组resultStack存储拓扑排序结果序列(存储下标)。 一边拓扑排序,一边计算ve。

遍历所有的出边,如果ve[前驱/起点]+边的权值>ve[后继/终点],更新ve的值

step2、求vl

把resultTop数组看成栈,汇点先出栈 初始化vl数组为即汇点的ve值,这个ve值是最大的

依次弹出resultTop数组的元素,按拓扑逆序求个顶点的vl值

当栈不为空时

resultStack出栈。inVex为起点,访问inVex为起点的所有终点。

取一个终点,计算起点的vl值。

如果vl[后继结点]-边<vl[前驱结点]

vl[inVex] = vl[后继结点]-边;

step3、从上往下扫描顶点表,处理每个顶点的边表,计算ee和el的值

活动最早开始时间ee,与当前边起点的ve值相等

活动最晚开始时间el,为当前边终点的vl值-边的权值

如果ee==el,输出关键路径

题目要求

通过求关键路径,计算完成整个工程至少需要多少时间

输入输出

第一行输入有向图的顶点个数和边的个数

下面多行输入边的起点和终点的编号和边的权值

输出第一行为所有关键路径的边。如果不能求关键路径,则输出“存在环,无关键路径。”

测试数据1

,

输入 9 11

0 1 6

0 2 4

0 3 5

1 4 1

2 4 1

3 5 2

4 6 9

4 7 7

5 7 4

6 8 2

7 8 4

输出

<0,1>6 <1,4>1 <4,7>7 <4,6>9 <6,8>2 <7,8>4

测试数据2

关键路径测试用例2

输入: 4 5

0 1 2

1 2 3

2 0 4

0 3 5

2 3 7

输出:

存在环,无关键路径。

代码

#include <iostream>
#include <stdio.h>
using namespace std;
const int MAX = 30;
struct ArcNode
{
    int weight;
    int adjvex;
    ArcNode* next;
};
struct VertexNode
{
    int in; //入度
    char vertex;  //图的顶点
    ArcNode* firstEdge;  //指向第一个边表
};
class ALGraph
{
private:
    VertexNode* adjList; //邻接表
    int vertexNum, arcNum;
    int* ve, * vl; //分别为事件最早发生时间的数组,事件最迟发生时间的数组
public:
    ALGraph(char v[], int n, int e);
    ~ALGraph();
    void inputEdges();
    bool setEdge(int vi, int vj, int weight); //设置边
    void display();
    bool Topological(int result[], int& count); //拓扑排序
    bool GriticalPath();  //求关键路径
};
ALGraph::ALGraph(char v[], int n, int e)
{
    vertexNum = n;
    arcNum = e;
    adjList = new VertexNode[vertexNum]; //创建
    for (int i = 0; i < vertexNum; i++)
    {
        adjList[i].in = 0;
        adjList[i].vertex = v[i];
        adjList[i].firstEdge = NULL;
    }
    ve = new int[vertexNum];
    vl = new int[vertexNum];
}

void ALGraph::inputEdges()
{
    //cout << "输入" << endl;
    for (int i = 0; i < arcNum; i++)
    {
        int vi, vj, weight;
        cin >> vi >> vj >> weight;
        if (!setEdge(vi, vj, weight))
        {
            cout << "超过范围,重新输入" << endl;
            i--;
        }
    }
}
bool ALGraph::setEdge(int vi, int vj, int weight)
{
    ArcNode* s;
    if (vi >= 0 && vi < vertexNum && vj >= 0 && vj < vertexNum && vi != vj)
    {
        //创建一个边结点vj
        s = new ArcNode;
        s->adjvex = vj;
        s->weight = weight;
        // 把边结点vj插入顶点表的vi项的邻接表中,成为第一个节点
        s->next = adjList[vi].firstEdge;
        adjList[vi].firstEdge = s;
        // 入度+1
        adjList[vj].in++;
        return true;
    }
    else
    {
        return false;
    }
}
ALGraph::~ALGraph()
{
    ArcNode* p, * pre;
    // 顶点表指向所有边的结点删除
    for (int i = 0; i < vertexNum; i++)
    {
        p = adjList[i].firstEdge;
        adjList[i].firstEdge = NULL;
        while (p)
        {
            pre = p;
            p = p->next;
            delete pre;
        }
    }
    delete[] adjList;
    delete[] ve;
    delete[] vl;
}
bool ALGraph::Topological(int result[], int& count)
{
    int stack[MAX];
    int top = -1;
    int inVex;
    int outVex;
    ArcNode* p;
    for (int i = 0; i < vertexNum; i++)
    {
        ve[i] = 0;
    }
    for (int i = 0; i < vertexNum; i++)
    {
        if (adjList[i].in == 0)
        {
            stack[++top] = i;
        }
    }
    count = 0;  //统计有多少个顶点被处理过了
    while (top != -1)
    {
        inVex = stack[top--]; //出栈
        result[count] = inVex; //存入数组
        count++; //下一次存储
        //找出当前处理的顶点的所有边
        p = adjList[inVex].firstEdge;
        while (p)  //扫描边表 “删掉”边
        {
            outVex = p->adjvex; //获取当前边表的终点值
            adjList[outVex].in--; //入度减1
            if (adjList[outVex].in == 0) //把入度为0的压入堆栈
                stack[++top] = outVex;
            if (ve[inVex] + p->weight > ve[outVex]) //找到最大的时间
                ve[outVex] = ve[inVex] + p->weight;
            p = p->next;
        }
    }
    // 判断排序是否正确
    if (count == vertexNum) //该拓扑排序是无环图
    {
        return true;
    }
    else
        return false;

}
bool ALGraph::GriticalPath()
{
    int resultStack[MAX]; //存储拓扑排序的序列的栈
    int resultTop;  //栈顶指针
    ArcNode* p;
    int i, count;
    int inVex, outVex;
    if (!Topological(resultStack, count))
    {
        return false;
    }
    
    resultTop = count - 1; //指向栈顶
    inVex = resultStack[resultTop--]; //栈顶元素出栈
    //求Vl数组,倒序求最小值
    //初始化
    for (i = 0; i < vertexNum; i++)
    {
        vl[i] = ve[inVex];
    }
    while (resultTop != -1)
    {
        inVex = resultStack[resultTop--];
        p = adjList[inVex].firstEdge;
        while (p)
        {
            outVex = p->adjvex;
            if (vl[inVex] > vl[outVex] - p->weight)
            {
                vl[inVex] = vl[outVex] - p->weight;
            }
            p = p->next;
        }
    }
   
    for (inVex = 0; inVex < vertexNum; inVex++)//从上往下扫描边表
    {
        p = adjList[inVex].firstEdge;
        while (p)
        {
            outVex = p->adjvex;
            int weight = p->weight;
            int ee = ve[inVex]; //活动的最早开始时间
            int el = vl[outVex] - weight;  //活动的最晚开始时间
            if (ee == el)
            {
                cout << "<" << inVex << "," << outVex << ">" << weight << " ";
            }
            p = p->next;
        }
    }
    return true;
}
int main()
{
    char vertex[MAX];
    int num, edge;

    cin >> num >> edge;
    for (int i = 0; i < num; i++)
    {
        vertex[i] = i + '0';
    }
    ALGraph graph(vertex, num, edge);
    graph.inputEdges();
    //graph.display();
    if (!graph.GriticalPath())
    {
        cout << "存在环,无关键路径。";
    }

    // 自动调用析构函数释放程序
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1825368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

零代码本地搭建AI大模型,详细教程!普通电脑也能流畅运行,中文回答速度快,回答质量高

这篇教程主要解决&#xff1a; 1). 有些读者朋友&#xff0c;电脑配置不高&#xff0c;比如电脑没有配置GPU显卡&#xff0c;还想在本地使用AI&#xff1b; 2). Llama3回答中文问题欠佳&#xff0c;想安装一个回答中文问题更强的AI大模型。 3). 想成为AI开发者&#xff0c;开…

家里满是“飞尘、毛絮”怎么办?用空气净化器,干净又卫生!

随着气温的升高&#xff0c;家中的毛絮和飞尘问题愈发严重&#xff0c;这些细小的颗粒常常聚集在房间的角落&#xff0c;即使每日清洁&#xff0c;似乎也难以彻底清除&#xff0c;反而可能使情况恶化。特别是对于养宠物的家庭来说&#xff0c;毛絮问题尤为突出&#xff0c;即使…

FullCalendar日历组件集成实战(15)

背景 有一些应用系统或应用功能&#xff0c;如日程管理、任务管理需要使用到日历组件。虽然Element Plus也提供了日历组件&#xff0c;但功能比较简单&#xff0c;用来做数据展现勉强可用。但如果需要进行复杂的数据展示&#xff0c;以及互动操作如通过点击添加事件&#xff0…

LVGL:

LVGL&#xff08;little video graphics library&#xff09;是一个开源的嵌入式图形库&#xff0c;提供高性能、低资源占用的图形用户界面&#xff08;GUI&#xff09;。具有模块化&#xff08;项目工程源码&#xff09;设计&#xff0c;可以在多平台使用&#xff08;如微处理…

《幻影大师:透视缠中说禅的虚像与真相》

而且他从不犯错&#xff0c;至少在他的叙述中是这样&#xff0c;所有的文章和言论都被粉饰得完美无瑕&#xff0c;即便有误&#xff0c;他也绝不公开承认&#xff0c;更别提什么真诚的道歉和改正了。那些对他推崇备至的人&#xff0c;多是盲目追随&#xff0c;将他神化为无所不…

Vue部分文件说明

1.eslintignore文件 Eslint会忽略的文件 # Eslint 会忽略的文件.DS_Store node_modules dist dist-ssr *.local .npmrc 2.gitignore # Git 会忽略的文件.DS_Store node_modules dist dist-ssr .eslintcache# Local env files *.local# Logs logs *.log npm-debug.log* yarn-de…

echarts写某个市地图

const geoJSON {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"adcode":440303,"name":"罗湖区","center":[114.123885,22.555341],"…

简单易用的多功能图床Picsur

什么是 Picsur &#xff1f; Picsur 是一款易于使用、可自行托管的图片分享服务&#xff0c;类似于 Imgur&#xff0c;并内置转换功能。支持多种格式的图片&#xff0c;包括 QOI、JPG、PNG、WEBP&#xff08;支持动画&#xff09;、TIFF、BMP、GIF&#xff08;支持动画&#xf…

AI Agent智能应用从0到1定制开发(完结)

在数字化时代的浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;代理智能应用如同星辰般璀璨&#xff0c;引领着技术革新的潮流。从零开始定制开发一款AI Agent智能应用&#xff0c;就像是在无垠的宇宙中绘制一颗新星的轨迹&#xff0c;每一步都充满了挑战与创新的火花。…

调试了一下午,终于把tailwindcss搞进Blazor了

在Vue和Uniapp项目中使用tailwindcss后&#xff0c;实在是太香了&#xff0c;非常符合我这从XAML走过来的老程序员的手感&#xff0c;所以老想着在Blazor项目中引入。看了几个老外大佬的视频&#xff0c;调试了一下午&#xff0c;终于是捣鼓成功了。由于咱们Blazor项目不在node…

[vue3]极速上手

介绍 vue3官网: Vue.js - 渐进式 JavaScript 框架 | Vue.js 1.0更容易维护 支持组合式API 可以减少代码量, 并且分散式维护转为集中式维护, 更容易封装复用 友好的TS支持 vue3的源码都被TS重写, 所以对TS的支持更友好 2.0更快的速度 新的diff算法, 模版编译优化, 更高效的…

【FireSim/Chipyard】解决FireSim Repo Setup步骤中Conda的firesim环境下载失败的问题

【FireSim/Chipyard】解决FireSim Repo Setup步骤中Conda的firesim环境下载失败的问题 问题描述 按照U250官方文档下载Conda环境的时候&#xff0c;即语句./scripts/machine-launch-script.sh --prefix REPLACE_ME_USER_CONDA_LOCATION的时候会遇到以下报错&#xff1a; Sol…

Locust web性能测试实践

Locust web性能测试实践 Locust 是一个开源的负载测试工具&#xff0c;使用Python语言实现&#xff0c;其简洁、轻量、高效的并发机制基于Gevent协程&#xff0c;可以实现单机模拟生成较高的并发压力。具有分布式和可扩展的特点&#xff0c;能够帮助你评估系统的性能并找到潜在…

【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;栈和队列相关知识 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀模板进阶 &#x1f9e9;<&…

ArrayList集合+综合案例

数组与集合的区别 ArrayList 概述 是java编写好的一个类,用于表示一个容器,使用的时候,需要注意指定容器中元素的数据类型;(如果不指定,语法不报错,但是取值的时候不方便)注意事项 使用的时候,写ArrayList<元素的数据类型>的数据类型的时候,带着泛型;使用ArrayList集合…

一文搞定 大语言模型(LLM)微调方法

引言 众所周知&#xff0c;大语言模型(LLM)正在飞速发展&#xff0c;各行业都有了自己的大模型。其中&#xff0c;大模型微调技术在此过程中起到了非常关键的作用&#xff0c;它提升了模型的生成效率和适应性&#xff0c;使其能够在多样化的应用场景中发挥更大的价值。 那么&…

linux如何部署前端项目和安装nginx

要在Linux上部署前端项目并安装Nginx&#xff0c;你可以按照以下步骤操作&#xff1a; 安装Nginx: sudo apt update sudo apt install nginx 启动Nginx服务: sudo systemctl start nginx 确保Nginx服务开机自启: sudo systemctl enable nginx 部署前端项目&#xff0c;假设前…

【scikit-learn入门指南】:机器学习从零开始

1. 简介 scikit-learn是一款用于数据挖掘和数据分析的简单高效的工具&#xff0c;基于NumPy、SciPy和Matplotlib构建。它能够进行各种机器学习任务&#xff0c;如分类、回归和聚类。 2. 安装scikit-learn 在开始使用scikit-learn之前&#xff0c;需要确保已经安装了scikit-le…

物联网模型

1.1 流模型源码 到OneNote Makefile出错:build/output/paho_c_version 先make clean移除bulid/output内的动态库,再make就会看到出错,将build/output的动态库文件命名以 . so结束,再次make就不会出错了。在sudo make install 安装在usr/local/lib中 修改main.c文件之后,…

火车头采集织梦发布模块插件下载及教程

火车头采集网页数据发布到织梦CMS&#xff08;DeDeCMS&#xff09;系统操作步骤如下&#xff1a; 1. 火车头采集织梦DeDeCMS发布模块下载安装 百度网盘&#xff1a;火车头采集织梦CMS发布插件下载地址 提取码&#xff1a;414h 2. 在火车头采集软件导入织梦De…