实现求关键路径的算法.docx
《实现求关键路径的算法.docx》由会员分享,可在线阅读,更多相关《实现求关键路径的算法.docx(17页珍藏版)》请在冰豆网上搜索。
实现求关键路径的算法
第一章需求分析
1.1题目内容与要求
内容:
自拟定合适的方式从键盘上输入一个AOE网,使用图的邻接表存储结构存储该AOE网,然后求出该AOE网的关键路径。
输入AOE网的方式要尽量的简单方便,程序要能够形象方便地观察AOE网和它的关键路径
基本要求:
1.熟悉图的存储结构及操作方式;
2.熟悉求关键路径的算法;
3.熟练运用开发环境;
4.完成软件的设计与编码;
5.熟练掌握基本的调试方法;
6.提交符合课程设计规范的报告;
1.2题目理解与功能分析
该题实质要求用数据结构中的图形知识编写一个求无循环有向帯权图中从起点到终点所有路径,经分析、比较求出长度最大路径,从而求出关键路径。
通常我们用有向图表示一个工程。
在这种有向图中,用顶点表示活动,用有向边表示活动Vi必须先于活动Vj进行。
如果在这种图中用有向边表示一个工程中的各项活动(ACTIVITY),用有向边上的权值表示活动的持续时间(DURATION),用顶点表示事件(EVENT),则这种的有向图叫做用边表示活动的网络,简称AOE网络。
在AOE网络中,从源点到各个顶点,可能不止一条。
这些路径的长度也可能不同。
不同路径所需的时间虽然不同,但只有各条路径上所有活动都完成了,这个工程才算完成。
因此,完成整个工程所需的时间取决于从源点到汇点的最长路径长度,即在这条路径上所有活动的持续时间之和。
这条路径长度就叫做关键路径(criticalpath)。
程序所要达到的功能:
输入并建立AOE网;输出关键活动并求出这个工程的关键路径;求出完成这个关键路径的最少时间并输出,该程序结束。
第二章概要设计
2.1设计思路
基本设计思路:
(1)以某一个求无循环有向帯权图蓝本。
(2)观察并记录这个图中每个弧的起始点及权值。
(3)用记录的结果建立AOE网,即边表示活动的网络,并用图的形式表示。
(4)用领接表来存储图这些信息。
(5)用Create()函数建立AOE图。
(6)用CriticPath()函数求出最大路径,并打印出关键路径。
(7)编写代码并测试。
2.2系统模块图
图2-1系统模块图
第三章详细设计
3.1图存储结构的建立
1.先建立邻接表的存储单元,为建立邻接表做准备。
为图中每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边(对于有向图是以vi为尾的弧)。
每个结点由3个域组成,其中邻接域(adjvex)指示与顶点vi邻接的点在图中的位置,链域(nextedge)指示下一条边或弧的结点,权值域(W)存储边或弧的权值大小。
在表头结点除了设有链域(firstedge)指向链表中第一个结点之外,还设有存储顶点v或其他有关的数据域(data)和存储顶点入度的域(id)(代码如下)。
typedefstructnode{
intadjvex;
intw;
structnode*nextedge;
}edgenode;
typedefstruct{
chardata;
intid;
edgenode*firstedge;
}vexnode;
2.然后构造有向图。
第一,输入顶点信息存储在顶点表中,并初始化该顶点的便表。
第二,首先输入边所依附的两个顶点的序号i和j然后生成新的邻接点序号为j的边表结点,最后将该结点插入到第i个表头部。
(代码如下)
for(intk=0;kscanf("%d,%d,%d",&begin,&end,&duttem);
p=(edgenode*)malloc(sizeof(edgenode));
p->adjvex=end-1;
p->w=duttem;
Graph[end-1].id++;
p->nextedge=Graph[begin-1].firstedge;
Graph[begin-1].firstedge=p;
3.2求关键路径的算法
利用AOE网进行工程管理时,需解决的两个主要问题:
其一,计算完成整个工程的最短工期;其二,确定关键路径,以找出哪些活动时影响工程进度的关键。
计算其过程必须分别在拓扑有序和逆拓扑有序的前提下进行。
也就说,ve[k]必须在事件vk所有前驱的最早发生的时间求得之后才能确定。
因此,可以在拓扑排序的基础上计算ve[k]和vl[k]。
由此得到求解关键路径的方法:
首先输入e条有向边,建立AOE网的邻接表存储结构;然后从始点出发,令事件的最早发生时间为0,按拓扑有序求其余各顶点时间的最早发生时间ve[k];
其次从终点出发,令事件最迟发生时间等于其最早发生时间,按你你逆拓扑排序求其余各顶点事件最迟发生时间vl[k]。
最后根据各顶点事件的ve和vl值,求所有活动最早开始时间ee和最迟开始时间el。
如果某活动满足条件ee=el,则为关键活动。
if(el[i]==ee[i]){
printf("此弧为关键活动");
}
同时,为计算各顶点事件的ve值是在拓扑排序的过程中进行的,因此需一个队列来记录拓扑排序,如果顶点的入度为0,则该顶点从队尾进入队列,拓扑排序时,从队头出队列。
第四章调试分析
在调试的过程中,遇到过很多的问题,我觉得在这之中的比较具有技术含量的问题就是在求事件的最晚发生的时间时,不知如何着手,然后我翻阅了一些书籍和上网查了一些资料,最后确定采用堆栈的方法,保存拓扑排序的顶点序列,然后根据后进先出的原则,进行求事件的最晚发生世间的计算。
另外就是很多的特殊情况需要考虑,比如说有向图含有环,这种情况是要编程加以排除,开始事件有多个,关键路径不只一条等一些很细节的问题。
第一组测试方案及结果如下:
图4-1建立有向图
图4.2邻接表输出图
图4.3关键路径输出图
第二组测试方案及结果如下:
图4.4建立有向图
图4.5邻接表输出图
图4.6关键路径输出图
参考文献
[1]严蔚敏编.数据结构(C语言版).北京:
清华大学出版社,1997.2
[2]谭浩强著.C语言程序设计(第二版).北京:
清华大出版社,2001.3
[3]夏克俭编著.数据结构.北京:
国防工业出版社,2000.7
[4]彭勃.数据结构.北京:
电子工业出版社,2007.6
[5]宜晨编著.VisualC++5.0实用培训教程.北京:
电子工业出版社,1998.5
附录(程序清单)
#include
#include
usingnamespacestd;
typedefstringVertex;
typedefintInfroType;/*存放权值*/
#definenull0
constintMAXN=20;
typedefstructArcNode
{
intadjvex;
structArcNode*nextarc;
InfroTypeInfro;
boolflag;
}ArcNode;
typedefstructVNode
{
Vertexdata;
intcount;
ArcNode*firstarc;
}VNode;
typedefstructGraph
{
VNodeadjlist[MAXN];
intn,e;
}Graph;
intvisited[MAXN];
intve[MAXN];
intvl[MAXN];
intstack[MAXN];
intst[MAXN];
inttp;
inttop;
intmax(intx,inty)
{
if(x>y)returnx;
elsereturny;
}
intmin(intx,inty)
{
if(xreturnx;
elsereturny;
}
voidInit(Graph*&g)
{
inti;
g->n=0;
g->e=0;
for(i=0;i{
g->adjlist[i].firstarc=null;
g->adjlist[i].count=0;
}
}
boolEmpty(Graph*g)
{
if(g->n==0&&g->e==0)
returntrue;
else
returnfalse;
}
intFind(Graph*g,Vertexv)
{
if(Empty(g))
{
cout<<"图为空."<return-1;
}
inti;
for(i=0;in;i++)
if(g->adjlist[i].data==v)
returni;
return-1;
}
voidCreate(Graph*&g)
{
inti,i1,i2;
Vertexv1,v2;
InfroTypew;
cout<<"请输入顶点数和边数:
"<cin>>g->n>>g->e;
cout<<"请输入顶点:
"<for(i=0;in;i++)
cin>>g->adjlist[i].data;
ArcNode*p;
p=null;
cout<<"边和权值:
"<for(i=1;i<=g->e;i++)
{
cin>>v1>>v2>>w;
i1=Find(g,v1);
i2=Find(g,v2);
if(i1!
=-1&&i2!
=-1)
{
p=newArcNode;
p->adjvex=i2;
p->Infro=w;
p->flag=false;
p->nextarc=g->adjlist[i1].firstarc;
g->adjlist[i1].firstarc=p;
}
}
}
voidOutput(Graph*g)/*按照存储结构输出*/
{
if(Empty(g))
{
cout<<"图为空."<return;
}
inti;
cout<<"按照邻接表输出为:
"<for(i=0;in;i++)
{
cout<adjlist[i].data;
ArcNode*p;
p=g->adjlist[i].firstarc;
while(p)
{
cout<<"--->"<<"("<adjlist[p->adjvex].data<<","<Infro<<")";
p=p->nextarc;
}
cout<}
}
boolTopsort(Graph*g,int&infro,int&v)
{
intstack[MAXN];
intst[MAXN];
inttp;
inttop;
memset(ve,0,sizeof(int)*MAXN);
Rdu(g,1);
top=-1;
tp=-1;
inti,m,n;
ArcNode*p;
p=null;
infro=0;
for(i=0;in;i++)
if(g->adjlist[i].count==0)
{
if(g->adjlist[i].firstarc&&infroadjlist[i].firstarc->Infro)
{
infro=g->adjlist[i].firstarc->Infro;
}
}
m=v;
for(i=0;in;i++)
if(!
g->adjlist[i].firstarc)
{
if(v==m)
v=i;
else
{
cout<<"结束事件不唯一."<returnfalse;
}
}
if(infro==-1)
{
cout<<"没有找到开始事件."<returnfalse;
}
if(v==-1)
{
cout<<"没有找到结束事件."<returnfalse;
}
for(i=0;i<=g->n;i++)
vl[i]=INT_MAX;
for(i=0;in;i++)
if(g->adjlist[i].count==0)
stack[++top]=i;
while(top+1)
{
m=stack[top--];
g->adjlist[m].count=-1;
st[++tp]=m;
p=g->adjlist[m].firstarc;
while(p)
{
n=p->adjvex;
if(g->adjlist[n].count==-1)
{
cout<<"该有向图有环."<returnfalse;
}
ve[n]=max(ve[n],ve[m]+p->Infro);
g->adjlist[n].count--;
if(g->adjlist[n].count==0)
stack[++top]=n;
p=p->nextarc;
}
}
if(tpn-1)
{
cout<<"拓扑排序不成功."<returnfalse;
}
else
cout<<"拓扑排序成功."<vl[tp]=ve[tp];
tp--;
while(tp+1)
{
p=g->adjlist[st[tp]].firstarc;
while(p)
{
vl[st[tp]]=min(vl[st[tp]],vl[p->adjvex]-p->Infro);
p=p->nextarc;
}
p=g->adjlist[st[tp]].firstarc;
while(p)
{
if(vl[st[tp]]==vl[p->adjvex]-p->Infro)
p->flag=true;
p=p->nextarc;
}
tp--;
}
returntrue;
}
voidPath(Graph*g,intu,intv,intpath[],intd)
{
intj,i;
ArcNode*p;
p=null;
visited[u]=1;
d++;
path[d]=u;
p=g->adjlist[u].firstarc;
if(u==v)
{
cout<<"关键路径:
";
cout<adjlist[path[0]].data;
for(i=1;i<=d;i++)
cout<<"--->"<adjlist[path[i]].data;
cout<<"总工期:
"<}
while(p)
{
j=p->adjvex;
if(visited[j]==0&&vl[j]==ve[j]&&p->flag)
Path(g,j,v,path,d);
p=p->nextarc;
}
visited[u]=0;
d--;
}
voidCriticPath(Graph*g)
{
if(Empty(g))
{
cout<<"图为空."<return;
}
intinfro,v,i;
infro=-1;
v=-1;
if(Topsort(g,infro,v)==false||infro==-1||v==-1)
{
return;
}
Rdu(g,1);
for(i=0;in;i++)
if(g->adjlist[i].count==0&&g->adjlist[i].firstarc&&g->adjlist[i].firstarc->Infro==infro)
{
memset(visited,0,sizeof(int)*MAXN);
memset(st,0,sizeof(int)*MAXN);
Path(g,i,v,st,-1);
}
}
voidMenu()
{
cout<<"有向网的相关操作"<cout<<"新建有向网-------------------->1"<cout<<"输出邻接表-------------------->2"<cout<<"输出关键路径------------------>3"<cout<<"退出-------------------------->0"<cout<<"请输入..."<}
voidmain()
{
Graph*g;
g=newGraph;
Init(g);
intchoose;
while
(1)
{
Menu();
cin>>choose;
switch(choose)
{
case1:
Init(g);Create(g);break;
case2:
Output(g);break;
case3:
CriticPath(g);break;
default:
return;
}
}
}
课程设计总结:
从这次给我的课程设计任务中我深刻地体会到了。
任何事情并不是想我们想的那样简单和容易。
只有扎扎时时的学习知识才能解决实际生活中的实际问题。
从这次课设中也让我明白以后要多了解相关知识,更多的做一些实践动手活动,将知识运用到实际问题中。
同时,增加自己的动手能力,这样才能便于我们更好的解决问题。
为今后打下好的基础。
在这个过程中遇到过很多的问题,有时候需要反复的修改和测试程序,在编程的过程中,随时会发现新的问题,随时要更改和调试程序。
我觉得在调试的过程中,最开始的时候出现有多条关键路径的图但是程序却只输出一条关键路径,我仔细查看程序,发现每个事件的最早开始时间和最迟开始时间是没有问题的,最终确定在采用有向图的深度优先遍历的时候,没有将访问标志数组恢复,所以只能输出一条关键路径。
当我编完程序后,我感到很欣喜,这毕竟是我的第一次课程设计任务。
能完成让我感到什么叫做编程的快乐,什么叫做乐趣。
督促我更加努力的学习我的专业课:
计算机科学与技术。
指导教师评语:
指导教师(签字):
年月日
课程设计成绩