期末关键路径大作业

题目要求

完成一部电影需要很多环节,具体环节如下:
a) 项目启动到确定导演需要 1 个时间,已确定导演到完善细节需要 2 个时间,已经完善细
节到开始拍摄需要 2 个时间。
b) 项目启动到确定演员需要 3 个时间,已确定导演到已确定演员需要 1 个时间,已确定演
员到开始拍摄需要 2 个时间。
c) 项目启动到完成场地确认需要 5 个时间,完成演员确定到完成场地确认需要 1 个时间,
完成场地确认到完善细节需要 1 个时间,完成场地确认到开始拍摄需要 2 个时间。
请选择合适的结构,设计算法,计算出从项目启动到开始拍摄之间至少需要多少个时间?
要求:

  1. 请画出完成一部电影的 AOE 图,找出关键路径,计算出从项目启动到开始拍摄之间至
    少需要多少个时间。
  2. 请将你的思路用代码来实现,并输出关键路径,以及计算出从项目启动到开始拍摄之间
    至少需要多少个时间。(需要代码和运行结果的截图)
  3. 请分析算法的效率,至少包括时间复杂度和空间复杂度等

解答

1.AOE图等的呈现

在这里插入图片描述

2.代码以及截图

#include<stdio.h>
#include<stdlib.h>
#include<string.h> 
#define MVNum 10 //最大顶点数,实际只有6个。 
int indegree[MVNum];//统计入度个数的数组。 

int Stack1[MVNum];//临时栈 

int Stack2[MVNum];//装topo序列 

int Queue[MVNum*2];//装最终的关键路径下标与权值

int lowestsumtime=0;//最终最短路径的和 

int etv[MVNum],ltv[MVNum];//装事件(顶点)发生最早所需要的时间 和  事件(顶点)发生最迟所需要的时间。 (事件的发生时间:从最初开始 到 某个事件开始发生) 

int top1=-1,top2=-1,rear=0,front=0;//和栈与队列相关的信息 

typedef int VerTexType; //顶点类型 

/*边结点 */ 
typedef struct ArcNode//边结点 
{
    int adjvex;//装的是弧头指向顶点的下标     
	int weight;//弧的权重    
    struct ArcNode *nextarc;
}ArcNode; 

/*顶点结点*/ 
typedef struct VNode 
{ 
    VerTexType data; 
    char name[20];
    ArcNode *firstarc; 
}VNode, AdjList[MVNum];

/*图的信息结构体*/ 
typedef struct INF
{ 
    AdjList vertices; //邻接表
	AdjList converse_vertices;//逆邻接表
    int vexnum, arcnum; //最大节点数、最大边数 
}Graph; 

/*创建有向图*/
int CreateDN(Graph *G) 
{
	int i,j,k; 
	int v1,v2,weight;
	int xb;
	char name[20]={0};
	ArcNode *p,*p2;
	/*输入顶点个数 和边的个数*/ 
	printf("请输入顶点总个数 和 弧的总条数:\n"); 
	scanf("%d%d",&G->vexnum,&G->arcnum); 
	
	/*给每一个顶点录入信息,包括下标,和名字字符串*/ 
	printf("请依次输入顶点(0...n-1)下标和已完成的活动名称(字符串)【中间必须只用一个空格隔开】 :\n");
	for(i=0;i<G->vexnum;i++)
	{
		scanf("%d %s",&xb,name);
				
		G->vertices[i].data=xb;//邻接表的表头信息 
		strcpy(G->vertices[i].name,name);
		
		G->converse_vertices[i].data=xb;//逆邻接表的表头信息 
		strcpy(G->converse_vertices[i].name,name);
		
		/*为什么要放个NULL? 因为后面 要用到头插法*/ 
		G->vertices[i].firstarc=NULL;
		G->converse_vertices[i].firstarc=NULL; 	
		
		memset(name,'\0',20);//将name数组全部用'\0'覆盖,也可以不用。	
	}
	
	printf("请连续依次输入两个顶点v1,v2与两顶点间的权值(三者以空格分开,其中v1—>v2)。\n"); 
	for(j=0;j<G->arcnum;j++)
	{
		scanf("%d%d%d",&v1,&v2,&weight);
		//创造结点,并让两个边指针分别指向两个不同结点。 
		p=(ArcNode*)malloc(sizeof(ArcNode));
		p2=(ArcNode*)malloc(sizeof(ArcNode));
		//给边结点成员赋值 
		p->adjvex=v2;//为了邻接表而准备  
		p->weight=weight;
		p2->adjvex=v1;//为了逆邻接表而准备,所以不用 给p2指向的结点赋权重。 
	
	   /*头插法操作。这里了体现了为什么顶点的data值是顶点的下标(从0开始),这样就不需要用自己写LocateVex函数定位顶点了*/	
		p->nextarc=G->vertices[v1].firstarc; 
		G->vertices[v1].firstarc=p;
		
		p2->nextarc=G->converse_vertices[v2].firstarc;
		G->converse_vertices[v2].firstarc=p2;		
	} 
	
} 
/*获取拓扑序列,并把topo序列存入栈Stack2,并获取最早事件发生数组etv[]*/ 
void getTopoStack2(Graph G)
{
	int i;
    int cur;
	ArcNode *p;
	/*首先把入度为0的顶点装入临时栈Stack1*/ 
	for(i=0;i<G.vexnum;i++)
	{
		if(indegree[i]==0)
		{
			Stack1[++top1]=i;
			indegree[i]--;
		}
	}
	//初始化etv
	memset(etv,0,sizeof(int)*MVNum); 
	
	while(top1!=-1)//栈不为空 
	{
		cur=Stack1[top1--];//出栈,顶点信息暂时用cur保存 
		Stack2[++top2]=cur;//将topo序列装入栈Stack2,以后好使用它获取 事件(顶点)发生最迟时间数组 ltv[]。这里可以用逆向邻接表吗? 
		p=G.vertices[cur].firstarc;
		while(p!=NULL)
		{
			indegree[p->adjvex]--;
			if(indegree[p->adjvex]==0)
			{
				Stack1[++top1]=p->adjvex;
			}			
			/*这里用到了贪心思想。etv[cur]:向量(起点下标0,终点下标cur),事件0发生 到 事件cur发生 所需的最早发生时间。 
			下面if语句理解:(起点下标0,终点下标cur)+(起点下标cur,终点p->adjvex) >  (起点下标0,终点p->adjvex)    */ 
			if(etv[cur]+p->weight > etv[p->adjvex])
			{
				etv[p->adjvex]=etv[cur]+p->weight;
			}
			p=p->nextarc;
		}	
	}
}
/*获取关键路径*/ 
void getCriticalPath(Graph G) 
{
	int i,xb;
	int gettop; 
	ArcNode *p;
	int ete,lte; 
	/*初始化 ltv[]数组,必须都是拓扑序列最后一个值(最后发生的事件),因为if语句*/ 
  	for(i=0;i<=top2;i++)
	{
		ltv[i]=etv[Stack2[top2]];//初始化为最大etv【最后一个结点】 
		
	} 
	/*获取ltv[]数组*/ 
	while(top2!=-1)//栈Stack2不为空     
	{
		gettop=Stack2[top2--];
		p=G.vertices[gettop].firstarc;
		while(p!=NULL)
		{
			if(ltv[gettop] > ltv[p->adjvex]-p->weight)
			{
				ltv[gettop]= ltv[p->adjvex]-p->weight;
			}		
		
			p=p->nextarc;
		}
	}
	
/*把 事件1、关键活动的权值、事件2 装入到队列里面,方便以后打印直观 */ 
	for(i=0;i<G.vexnum;i++)
	{
		xb=Stack2[i];
	    ete=etv[xb];//公式一:活动(边)最早发生时间 = 事件1(顶点)最早发生时间。 
	    p=G.vertices[xb].firstarc;
	    while(p!=NULL)
	    {
	    	lte=ltv[p->adjvex]-p->weight;//公式二:活动(边)最迟发生时间 = 事件2(顶点)最迟发生时间 - 权重(事件1-->事件2)
			/*利用这个条件,可以把 事件1、关键活动的权值、事件2都找到*/ 
	    	if(ete==lte)
	    	{
	    		/*存入队列以后好直观输入*/ 
	    		Queue[rear++]=xb;
	    		Queue[rear++]=p->weight;
	    		Queue[rear++]=p->adjvex;
	    		
	    		lowestsumtime+=p->weight;//最后输出最短消耗时间要用到。 
			}
	    	p=p->nextarc;
		}	    
	}
	
}
int main()
{
	int i,cnt;
	Graph G;
	int xb1,xb2,timecost;//最后打印所需 
	CreateDN(&G);
	//统计每个顶点的入度。
	for(i=0;i<G.vexnum;i++)
	{
		cnt=0;
		ArcNode* p=G.converse_vertices[i].firstarc;
		while(p!=NULL)
		{
			cnt++;
			p=p->nextarc;
		}
		indegree[i]=cnt;
	} 
	//获得拓扑序列
	getTopoStack2(G);
	//获得关键路径 
	getCriticalPath(G);
	
	/*把结果打印出来(可以不用)*/ 
	printf("关键路径为:"); 
	xb1=Queue[front++]; 
	timecost=Queue[front++];
	xb2=Queue[front++];	
	printf("%s--%d-->%s",G.vertices[xb1].name,timecost,G.vertices[xb2].name);
	
	for(i=0;front<rear;i++)
	{
		front++;
		timecost=Queue[front++];
		xb2=Queue[front++];	
		printf("--%d-->%s",timecost,G.vertices[xb2].name); 
	}
	printf("\n从项目启动到开始拍摄之间至少需要时间数为:%d\n",lowestsumtime) ;

	return 0;
}
/*测试数据入下*/ 
/* 
6 10
0 项目启动
1 完成导演确认
2 完成完善细节
3 开始拍摄
4 完成确定演员
5 完成场地确认
0 1 1
1 2 2
2 3 2
1 4 1
0 4 3
4 3 2
0 5 5
4 5 1
5 2 1
5 3 2
*/ 

在这里插入图片描述
注意:此程序编译环境是DevC++。

3.效率分析

时间复杂度分析

该程序最花费时间的三个步骤是 1.求拓扑序列 2.求 件最晚发生时间ltv[] 数组3.将关键路径装入队列Queue[]。因为只有他们都用到了双层循环嵌套结构,其他的最多就是单层循环。如图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
分析一下,假如一个AOE网有n个顶点,e条弧。那么上面的三个步骤都是外层循环把每个顶点都遍历一遍,时间复杂度是O(n),内层循环把每一条弧都遍历了一遍,时间复杂度是O(e)。所以整个程序时间复杂度应该是O(n+e)。

空间复杂度分析

呃,我不确定。教材上提到过,如果用邻接表和逆邻接表存储AOE网的顶点和弧,假设一个AOE网有n个顶点和e条弧,那么空间复杂度为O(n+e)。但是我感觉这些存储空间不可避免,多余的空间才算(就像简单排序算法的空间复杂度是O(1),但是还是需要O(n)的空间存放元素。)。这个程序用到了Stack1作为临时栈存放数据元素,所以时间复杂度是O(n)。

回顾几个问题

1.获得ete的公式是什么?获得lte的公式是什么?

2.etv[]与ltv[]是怎么得到的?

3.两个if语句的理解?
if(etv[cur]+p->weight > etv[p->adjvex])
{
etv[p->adjvex]=etv[cur]+p->weight;
}

if(ltv[gettop] > ltv[p->adjvex] - p->weight)
{
ltv[gettop]= ltv[p->adjvex] - p->weight;
}
4. 边结点中adjvex装的是什么? 弧头一方的顶点下标。
5. 为什么顶点结点的data用来装的是下标值?因为头插法需要定位顶点,这样就避免了自己写LocateVex函数定位顶点了。
6.可以用逆邻接表来实现其他功能吗?我使用逆邻接表主要是为了获取每个顶点的入度大小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值