图之关键路径

引入

在这里插入图片描述
在这里插入图片描述

事件是点,活动是边.

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

####

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

在这里插入图片描述

第一句话是说,最迟事件执行完后,后继的事件要么直接开始,要么可以等会开始,但不能最迟事件正在执行着,后继时间就得开始了,这样是不行的.
所以我们可以从已知的汇点从后往前递推,每一次求解弧头的最小值(因为值越小,说明你可以执行时间的时间越长).

举例:
在这里插入图片描述

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

在这里插入图片描述

活动的最早发生时间,只要这个事件可以开始做的时间已到,我们就开始做了,就像平常老师已布置作业你就可以写了.
活动的最晚发生时间,就是你得在下一个事件开始之前必须完成这个活动,也就是在老师截止之前你得完成作业.直接用截止时间-你写作业花费的时间便是你最晚写作业的时间.
为啥求活动最早发生是用事件的最早-的呢?
因为事件是从最早发生时间之后,你就可以搞活动了,在事件最晚发生之前开始都行,所以我最早开始做就是当事件可以开始搞的时候开始做就行了.

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

在这里插入图片描述

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

总结:

在这里插入图片描述

算法设计:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法时间复杂度分析:

在这里插入图片描述

参考代码:
#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
 
运行结果:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

注:

  1. 算法在执行求v数组时:假如图中A->B,D->B,C->B,那么我们采用按照拓扑序的顺序对每个点的临界点的V进行初始化,如果该临界点的V小于新的V则用大的那个进行覆盖.
    由于A,D,C都是在B之前的,所以当执行到B时,其V已经达到最大值了.这样的好处是遍历一遍邻接表V已经找到了.
  2. 如果采用按照拓扑序,来直接求解当前的V,那么需要找到该点入度的所有点.即需要建立逆邻接表.然后每次进行查找然后更新,较为麻烦.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱编程的大李子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值