引入


事件是点,活动是边.
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
运行结果:



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

被折叠的 条评论
为什么被折叠?



