图之关键路径

简介: 图之关键路径

引入


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,那么需要找到该点入度的所有点.即需要建立逆邻接表.然后每次进行查找然后更新,较为麻烦.


相关文章
|
人工智能 算法
图的应用——关键路径
图的应用——关键路径
408 0
图的应用——关键路径
|
3月前
|
BI
敏捷项目度量问题之利用「需求燃起图」和「缺陷燃起图」预测项目的完成时间如何解决
敏捷项目度量问题之利用「需求燃起图」和「缺陷燃起图」预测项目的完成时间如何解决
|
6月前
|
开发者
如何画好一张架构图/业务图/流程图,掌握4个关键点
本文分享了如何制作出有帮助的图表,强调了即使是开发者也需要良好的绘图技巧。文章列举了常见的图表类型,如代码实现图、技术架构图、业务流程图、技术链路图、交互时序图和业务架构图,并指出好的图表应具备结构清晰、外表美观和内容完整的特点。为了达到这些标准,作者推荐了设计的四大原则:亲密性、对齐、对比和重复,以及色轮的运用来提升美感。此外,还介绍了黄金分割构图法以增加视觉吸引力。最后,强调了以终为始的设计思路,确保图表能独立传达完整的信息,并鼓励读者实践这些技巧,提升工作和生活中的沟通效率。
如何画好一张架构图/业务图/流程图,掌握4个关键点
|
6月前
|
搜索推荐 测试技术 C语言
设计求解AOE网关键路径程序(详细设计,附完整实现代码)
设计求解AOE网关键路径程序(详细设计,附完整实现代码)
|
6月前
|
开发者
如何画好一张架构图/业务图/流程图,掌握这4个关键点
作为一个开发,日常工作中免不了要画一些图,无论是技术架构图还是业务流程图。基于个人的一些经验,作者分享了他的作图方法,给大家一点思路提供参考,希望在未来的工作、生活中都能有所帮助。
|
6月前
|
前端开发 测试技术
如何绘制wbs图
如何绘制wbs图
232 0
|
机器学习/深度学习 数据可视化
RNAseq|批量单因素生存分析 + 绘制森林图
RNAseq|批量单因素生存分析 + 绘制森林图
346 0
|
算法
关键路径
关键路径
|
人工智能 算法
【数据结构】什么的图的关键路径?关键路径相关概念?关键路径算法实现?
【数据结构】什么的图的关键路径?关键路径相关概念?关键路径算法实现?
489 0
【数据结构】什么的图的关键路径?关键路径相关概念?关键路径算法实现?