图之关键路径

简介: 图之关键路径

引入


20210531225059146.png

20210531225120458.png


事件是点,活动是边.

1.事件V1的最早发生时间ve[i]

20210531225208163.png

20210531225327423.png


2.事件的最迟发生时间vl[i]

20210531225344754.png


第一句话是说,最迟事件执行完后,后继的事件要么直接开始,要么可以等会开始,但不能最迟事件正在执行着,后继时间就得开始了,这样是不行的.

所以我们可以从已知的汇点从后往前递推,每一次求解弧头的最小值(因为值越小,说明你可以执行时间的时间越长).

举例:

20210531225457195.png

3.活动ai = <Vj,Vk >的最早发生时间e[i]

2021053122551361.png

活动的最早发生时间,只要这个事件可以开始做的时间已到,我们就开始做了,就像平常老师已布置作业你就可以写了.

活动的最晚发生时间,就是你得在下一个事件开始之前必须完成这个活动,也就是在老师截止之前你得完成作业.直接用截止时间-你写作业花费的时间便是你最晚写作业的时间.

为啥求活动最早发生是用事件的最早-的呢?

因为事件是从最早发生时间之后,你就可以搞活动了,在事件最晚发生之前开始都行,所以我最早开始做就是当事件可以开始搞的时候开始做就行了.


4.活动ai = <Vj,Vk >的最迟发生时间l[i]

2021053122572110.png


当活动最早开始时间和最晚开始时间一样,那么这个路径就是关键路径.它表示在执行这些事件的时候,一刻都不能停.

总结:

20210531225837453.png

算法设计:

20210531225927914.png

20210531225936808.png

20210531225944974.png

算法时间复杂度分析:

20210531230010663.png

参考代码:


#include <iostream>
#include<cstring>
#include<stack>
using namespace std;
const int MaxVnum=100;//顶点数最大值
int indegree[MaxVnum];//入度数组
int ve[MaxVnum];     //事件vi的最早发生时间
int vl[MaxVnum];     //事件vi的最迟发生时间   e和l数组不需要创建,因为我们在判断时如果满足就直接输出了,没必要再存着. 
typedef string VexType;//顶点的数据类型为字符型
typedef struct AdjNode{ //定义邻接点类型
int v;                 //邻接点下标
int weight;            //权值
struct AdjNode *next;  //指向下一个邻接点指针
}AdjNode;
typedef struct VexNode{ //定义顶点类型
VexType data;           //VexType为顶点的数据类型,根据需要定义
AdjNode *first;         //指向第一个邻接点指针
}VexNode;
typedef struct{ //包含邻接表和逆邻接表
    VexNode Vex[MaxVnum];          //定义邻接表
    VexNode converse_Vex[MaxVnum]; //定义逆邻接表
    int vexnum,edgenum;           //顶点数,边数
}ALGragh;
int locatevex(ALGragh G,VexType x)
{
    for(int i=0;i<G.vexnum;i++)//查找顶点信息的下标
       if(x==G.Vex[i].data)
        return i;
    return -1;//没找到
}
void insertedge(ALGragh &G,int i,int j,int w)//插入一条边
{
    AdjNode *s1,*s2;
    //创建邻接表结点
    s1=new AdjNode;
    s1->v=j;
    s1->weight=w;
    s1->next=G.Vex[i].first;
    G.Vex[i].first=s1;
    //创建逆邻接表结点
    s2=new AdjNode;
    s2->v=i;
    s2->weight=w;
    s2->next=G.converse_Vex[j].first;
    G.converse_Vex[j].first=s2;
}
void printg(ALGragh G)//输出邻接表
{
   cout<<"----------邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.Vex[i].first;
       cout<<G.Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<G.Vex[t->v].data<<" "<<t->weight<<"]     ";
           t=t->next;
       }
       cout<<endl;
   }
   cout<<"----------逆邻接表如下:----------"<<endl;
   for(int i=0;i<G.vexnum;i++)
   {
       AdjNode *t=G.converse_Vex[i].first;
       cout<<G.converse_Vex[i].data<<":  ";
       while(t!=NULL)
       {
           cout<<"["<<G.Vex[t->v].data<<" "<<t->weight<<"]     ";
           t=t->next;
       }
       cout<<endl;
   }
}
void CreateALGraph(ALGragh &G)//创建有向图的邻接表和逆邻接表
{
    int i,j,w;
    VexType u,v;
    cout<<"请输入顶点数和边数:"<<endl;
    cin>>G.vexnum>>G.edgenum;
    cout << "请输入顶点信息:"<<endl;
    for(i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
    {
        cin>>G.Vex[i].data;
        G.converse_Vex[i].data=G.Vex[i].data;
        G.Vex[i].first=NULL;
        G.converse_Vex[i].first=NULL;
    }
    cout<<"请依次输入每条边的两个顶点及权值u,v,w"<<endl;
    while(G.edgenum--)
    {
        cin>>u>>v>>w;
        i=locatevex(G,u);//查找顶点u的存储下标
        j=locatevex(G,v);//查找顶点v的存储下标
        if(i!=-1&&j!=-1)
            insertedge(G,i,j,w);
        else
        {
           cout << "输入顶点信息错!请重新输入!"<<endl;
           G.edgenum++;//本次输入不算
        }
    }
}
void FindInDegree(ALGragh G)//求出各顶点的入度存入数组indegree中
{
  int i,count;
  for(i=0;i<G.vexnum; i++)
    {
        count=0;
        AdjNode *p=G.converse_Vex[i].first;
    if(p)
    {
      while(p)
      {
        p=p->next;
        count++;
      }
    }
    indegree[i]=count;
  }
  cout<<"入度数组为:"<<endl;
  for(int i=0;i<G.vexnum;i++)//输出入度数组
       cout<<indegree[i]<<"\t";
    cout<<endl;
}
bool TopologicalSort(ALGragh G,int topo[]){
  int index;
  stack<int> S;
  FindInDegree(G);//入度数组进行 初始化 
  for(int i  = 0; i <G.vexnum; i++){
    if(!indegree[i]){
      S.push(i);//入度为0的入栈 
    }
  } 
  index = 0;
  while(!S.empty()){
    int x = S.top();
    S.pop();
    topo[index] = x;
    index++;
    AdjNode *p = G.Vex[x].first;//p指向i的第一个邻接点
    while(p){//x的邻接点进行入度-1 
      int k = p->v;
      --indegree[k];
      if(indegree[k]==0){
        S.push(k);
      }
      p = p->next;
    } 
  }
  if(index < G.vexnum){
    return false;
  }else{
    return true;//该有向图无回路. 
  }
}
bool CriticalPath(ALGragh G,int topo[]){
  int n,k;
  n = G.vexnum;
  if(TopologicalSort(G,topo)){
    cout<<"拓扑序列为:"<<endl;
    for(int i = 0; i < G.vexnum;i++){
      cout<<topo[i]<<"\t";
    } 
    cout<<endl;
  }else{
    cout<<"该图有环,无拓扑序列!"<<endl; 
  }
  for(int i = 0;i  < n; i++){
    ve[i] = 0;//进行初始化 
  } 
  for(int i = 0; i < n; i++){//按照拓扑序列求ve数组 
    k = topo[i];
    AdjNode *p = G.Vex[k].first;
    while(p!=NULL){
      int j = p->v;
      if(ve[j] < ve[k]+p->weight){
        ve[j] = ve[k]+p->weight;
      }
      p = p->next;//p后移 
    }
  } 
  for(int i = 0;i<n; i++){
    vl[i] = ve[n-1];//给每个事件的最迟发生时间置初值ve[n-1] 相当于给每个初始化为最大值. 
  }
  for(int i = n-1; i>=0; i--){//按逆拓扑序求 每个事件的最迟发生时间
    k = topo[i];
    AdjNode *p = G.Vex[k].first;
    while(p!=NULL) {
      int j = p->v;
      if(vl[k] > vl[j]-p->weight){
        vl[k] = vl[j]-p->weight;
      }
      p = p->next;//指针后移 
    }
  }
  cout<<"事件的最早发生时间和最迟发生时间:"<<endl;
  for(int i = 0;i < n; i++){
    cout<<ve[i]<<"\t"<<vl[i]<<endl;
  }
  //判断每一活动是否为关键活动
  cout<<endl;
  cout<<"关键活动路径为:";
  for(int i = 0; i < n;i++){
    AdjNode *p = G.Vex[i].first;
    while(p!=NULL){
      int j = p->v;
      int e = ve[i];
      int l = vl[j] - p->weight;
      if(e==l){
        cout<<"<"<<G.Vex[i].data<<","<<G.Vex[j].data<<">    ";
      }
      p = p->next;
    }
  } 
  return true;
} 
int main()
{
    ALGragh G;
    int *topo=new int[G.vexnum];
    CreateALGraph(G);//创建有向图的邻接表和逆邻接表
    printg(G);//输出邻接表和逆邻接表
    CriticalPath(G,topo);
    return 0;
}
//请输入顶点数和边数:
//9 11
//请输入顶点信息:
//V1 V2 V3 V4 V5 V6 V7 V8 V9
//请依次输入每条边的两个顶点及权值u,v,w
//V1 V2 6
//V1 V3 4
//V1 V4 5
//V2 V5 1
//V3 V5 1
//V4 V6 2
//V5 V7 9
//V5 V8 7
//V6 V8 4
//V7 V9 2
//V8 V9 4


运行结果:


20210531230238582.png


20210531230204374.png

20210531230223753.png

注:


算法在执行求v数组时:假如图中A->B,D->B,C->B,那么我们采用按照拓扑序的顺序对每个点的临界点的V进行初始化,如果该临界点的V小于新的V则用大的那个进行覆盖.

由于A,D,C都是在B之前的,所以当执行到B时,其V已经达到最大值了.这样的好处是遍历一遍邻接表V已经找到了.

如果采用按照拓扑序,来直接求解当前的V,那么需要找到该点入度的所有点.即需要建立逆邻接表.然后每次进行查找然后更新,较为麻烦.


相关文章
|
9月前
|
传感器 大数据 开发者
深入理解Python中的生成器和迭代器
在Python编程中,生成器和迭代器是实现懒计算和高效内存使用的重要工具。本文将详细探讨生成器和迭代器的概念、用法以及它们在实际开发中的应用场景。
|
存储 JSON 监控
在日志服务中使用权限助手进行权限配置
在使用 SLS 日志服务的时候,很多情况下,我们需要给不同子账号,角色赋予不同的权限。由于日志服务中涉及的功能模块很多,而且功能模块互相之间有依赖关系,所以手工编写 RAM 的配置文件会显得很繁琐。现在,日志服务中新上线了权限助手功能,使用这个工具可以在很大程度上简化权限配置的操作。
1698 0
在日志服务中使用权限助手进行权限配置
|
5月前
|
存储 边缘计算 人工智能
深入理解云计算:架构、类型与未来趋势
【10月更文挑战第6天】深入理解云计算:架构、类型与未来趋势
246 0
Google Earth Engine(GEE)——Export.image.toAsset/toDrive两者的区别和混用,正确导出分类样本数据到资产assets和引用
Google Earth Engine(GEE)——Export.image.toAsset/toDrive两者的区别和混用,正确导出分类样本数据到资产assets和引用
761 0
Google Earth Engine(GEE)——Export.image.toAsset/toDrive两者的区别和混用,正确导出分类样本数据到资产assets和引用
|
10月前
|
存储 NoSQL Redis
作者推荐 |【Redis技术进阶之路】「原理系列开篇」揭秘高效存储模型与数据结构底层实现(SDS)(三)
作者推荐 |【Redis技术进阶之路】「原理系列开篇」揭秘高效存储模型与数据结构底层实现(SDS)
102 0
|
运维 监控 Oracle
应届生运维简历攻略
应届生运维简历攻略
1150 0
|
运维 监控 Linux
云计算运维工程师简历怎么写?带简历案例
云计算运维工程师简历怎么写?带简历案例
1568 0
|
数据采集 Web App开发 安全
别去送死了。Selenium 与 Puppeteer 能被网站探测的几十个特征
别去送死了。Selenium 与 Puppeteer 能被网站探测的几十个特征
358 0
|
Java 应用服务中间件
java.lang.OutOfMemoryError: unable to create new native thread
<div class="markdown_views"> <h2 id="1问题起因">1、问题起因</h2> <p>这个异常问题本质原因是我们创建了太多的线程,而能创建的线程数是有限制的,导致了异常的发生。能创建的线程数的具体计算公式如下: </p> <pre class="prettyprint"><code class=" hljs fix"><span clas
3014 0
|
10月前
|
XML JSON 人工智能
探索Gin框架:Golang Gin框架请求参数的获取
探索Gin框架:Golang Gin框架请求参数的获取