图
-
拓扑排序
AOV网:有⼀个表示⼯程的有向图中, ⽤顶点表示活动, ⽤弧表示活动之间的优先关系,这样有向图为顶点表示活动的⽹. 我们称为AOV⽹(Activity On Vertex Network)
定义:设G = (V,E)是⼀个具有n个顶点的有向图,V中的顶点序列V1,V2,.....,Vn.若满⾜从顶点Vi到Vj有⼀条路径,则在顶点序列Vi必须在Vj之前,则我们称这样的顶点序列成为拓扑序列。所谓拓扑排序,其实就是对⼀个有向图构造拓扑序列的过程.
构造过程拓扑序列会产⽣2个结果:
1. 如果此⽹中的全部顶点被输出,则说明它不存在环(回路)的AOV⽹; 2. 如果输出的顶点数少了,哪怕仅少了⼀个,也说明这个网存在环(回路),不是AOV网。


思路:
从AOV⽹中选择⼀个⼊度为0的顶点输出,然后从删去此顶点,并删除以此顶点为尾的弧. 继续重复此步骤,直到输出全部顶点或AOV⽹中不存在⼊度为0的顶点为⽌. 1.创建⼀个栈(stack),⽤来存储⼊度in为0 的顶点序号; 2.遍历AOV图中顶点表,判断⼊度为0的顶点全部⼊栈;#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXEDGE 20 #define MAXVEX 14 #define INFINITYC 65535 /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ typedef int Status; /*邻接矩阵结构 */ typedef struct { int vexs[MAXVEX]; int arc[MAXVEX][MAXVEX]; int numVertexes, numEdges; } MGraph; /* 邻接表结构****************** */ //边表结点 typedef struct EdgeNode { //邻接点域,存储该顶点对应的下标 int adjvex; //用于存储权值,对于非网图可以不需要 int weight; //链域,指向下一个邻接点 struct EdgeNode *next; } EdgeNode; //顶点表结点 typedef struct VertexNode { //顶点入度 int in; //顶点域,存储顶点信息 int data; //边表头指针 EdgeNode *firstedge; } VertexNode, AdjList[MAXVEX]; //图结构 typedef struct { AdjList adjList; //图中当前顶点数和边数 int numVertexes,numEdges; } graphAdjList,*GraphAdjList; /*1.构成AOV网图*/ //构件图 void CreateMGraph(MGraph *G) { int i, j; /* printf("请输入边数和顶点数:"); */ G->numEdges=MAXEDGE; G->numVertexes=MAXVEX; /* 初始化图 */ for (i = 0; i < G->numVertexes; i++) { G->vexs[i]=i; } /* 初始化图 */ for (i = 0; i < G->numVertexes; i++) { for ( j = 0; j < G->numVertexes; j++) { G->arc[i][j]=0; } } G->arc[0][4]=1; G->arc[0][5]=1; G->arc[0][11]=1; G->arc[1][2]=1; G->arc[1][4]=1; G->arc[1][8]=1; G->arc[2][5]=1; G->arc[2][6]=1; G->arc[2][9]=1; G->arc[3][2]=1; G->arc[3][13]=1; G->arc[4][7]=1; G->arc[5][8]=1; G->arc[5][12]=1; G->arc[6][5]=1; G->arc[8][7]=1; G->arc[9][10]=1; G->arc[9][11]=1; G->arc[10][13]=1; G->arc[12][9]=1; }/*2.将AOV网图借助邻近矩阵转换成邻接表结构*/ void CreateALGraph(MGraph G,GraphAdjList *GL) { int i,j; EdgeNode *e; //创建图 *GL = (GraphAdjList)malloc(sizeof(graphAdjList)); //对图中的顶点数.弧数赋值 (*GL)->numVertexes=G.numVertexes; (*GL)->numEdges=G.numEdges; //读入顶点信息,建立顶点表 for(i= 0;i <G.numVertexes;i++) { (*GL)->adjList[i].in=0; (*GL)->adjList[i].data=G.vexs[i]; //将边表置为空表 (*GL)->adjList[i].firstedge=NULL; } //建立边表 for(i=0;i<G.numVertexes;i++) { for(j=0;j<G.numVertexes;j++) { if (G.arc[i][j]==1) { //创建空的边表结点 e=(EdgeNode *)malloc(sizeof(EdgeNode)); //邻接序号为j e->adjvex=j; // 将当前顶点上的指向的结点指针赋值给e e->next=(*GL)->adjList[i].firstedge; //将当前顶点的指针指向e (*GL)->adjList[i].firstedge=e; (*GL)->adjList[j].in++; } } } }/*3.拓扑排序. 若AOV网图无回路则输出拓扑排序的序列并且返回状态值1,若存在回路则返回状态值0*/ /*拓扑排序:解决的是一个工程能否顺序进行的问题!*/ Status TopologicalSort(GraphAdjList GL) { EdgeNode *e; int i,k,gettop; //用于栈指针下标 int top=0; //用于统计输出顶点的个数 int count=0; //建栈将入度为0的顶点入栈(目的:为了避免每次查找时都要遍历顶点表查找有没有入度为0的顶点) int *stack=(int *)malloc(GL->numVertexes * sizeof(int)); //1.遍历邻接表-顶点表,将入度in为0的顶点入栈 /*参考图1> 此时stack栈中应该成为0,1,3.即V0,V1,V3的顶点入度为0*/ for (i = 0; i<GL->numVertexes; i++) //将入度为0的顶点入栈 if(0 == GL->adjList[i].in) stack[++top]=i; printf("top = %d\n",top); //2.循环栈结构(当栈中有元素则循环继续) while(top!=0) { //出栈 gettop=stack[top--]; printf("%d -> ",GL->adjList[gettop].data); //输出顶点,并计数 count++; //遍历与栈顶相连接的弧 for(e = GL->adjList[gettop].firstedge; e; e = e->next) { //获取与gettop连接的顶点 k=e->adjvex; //1.将与gettop连接的顶点入度减1; //2.判断如果当前减1后为0,则入栈 if( !(--GL->adjList[k].in) ) //将k入栈到stack中,并且top加1; stack[++top]=k; } } /*思考:3 -> 1 -> 2 -> 6 -> 0 -> 4 -> 5 -> 8 -> 7 -> 12 -> 9 -> 10 ->13 -> 11 这并不是唯一的拓扑排序结果. 分析算法:将入度为0的顶点入栈的时间复杂度为O(n), 而之后的while 循环,每个顶点进一次栈,并且出一次栈. 入度减1, 则共执行了e次. 那么整个算法的时间复杂度为O(n+e)*/ printf("\n"); //判断是否把所有的顶点都输出. 则表示找到了拓扑排序; if (count < GL->numVertexes) return ERROR; else return OK; } -
关键路径
在⼀个表示⼯程的带权有向图中,⽤顶点表示事件,⽤有向边表示活动,⽤边上的权值表示活动的持续时间,这种有向图的边表表示活动的⽹,我们称之为AOE ⽹(Activity On Edge Network)
没有⼊边的顶点称为始点或源点; 没有出边的顶点称为终点或汇点; 由于⼀个⼯程, 总有⼀个开始,⼀个结束.所以正常情况下,AOE⽹只有⼀个源点和⼀个汇点.
路径上各个活动所持续的时间之和称为路径⻓度 从源点到汇点具有最⼤的路径叫关键路径 在关键路径上的活动叫关键活动关键路径求解过程⼏个核⼼参数
• 事件最早发⽣的时间 etv (earliest time of vertex): 即顶点Vk 的最早发⽣时间; • 事件最晚发⽣时间 ltv (latest time of vertex): 即顶点Vk 的最晚发⽣时间, 也就是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个⼯期; • 活动的最早开⼯时间 ete (earliest time of edge); 即弧Ak 的最早发⽣时间; • 活动的最晚开⼯时间 lte (latest time of edge); 即弧Ak的最晚发⽣时间,也就是不推迟⼯期的最晚开⼯时间。
思路:
求事件的最早发⽣时间etv的过程:就是从头到尾去找拓扑序列的过程. 所以在求解关键路径之前,我们需要调⽤⼀次拓扑排序的序列去计算etv 和拓扑序列列表.
事件最晚发⽣时间 ltv (latest time of vertex): 即顶点Vk的最晚发⽣时间,也就是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个工期。
活动的最早开⼯时间 ete (earliest time of edge);:即弧Ak的最早发⽣时间;
活动的最晚开⼯时间 lte (latest time of edge):即弧Ak的最晚发⽣时间,也就是不推迟⼯期的最晚开工时间。

时间复杂度:
拓扑排序:O(n+e); 初始化Itv:O(n); 计算Itv:O(n+e); 计算ete/Ite:O(n+e); 最终求得关键路径的时间复杂度:O(n+e)。#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXEDGE 30 #define MAXVEX 30 #define INFINITYC 65535 typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ /* 邻接矩阵结构 */ typedef struct { int vexs[MAXVEX]; int arc[MAXVEX][MAXVEX]; int numVertexes, numEdges; }MGraph; /* 邻接表结构****************** */ //边表结点 typedef struct EdgeNode { //邻接点域,存储该顶点对应的下标 int adjvex; //用于存储权值,对于非网图可以不需要 int weight; //链域,指向下一个邻接点 struct EdgeNode *next; }EdgeNode; //顶点表结点 typedef struct VertexNode { //顶点入度 int in; //顶点域,存储顶点信息 int data; //边表头指针 EdgeNode *firstedge; }VertexNode, AdjList[MAXVEX]; typedef struct { AdjList adjList; //图中当前顶点数和边数 int numVertexes,numEdges; }graphAdjList,*GraphAdjList; /* **************************** */ /* 关于AOE网图的存储代码段-Begin */ //1.完成AOE网图关于邻接矩阵的存储 void CreateMGraph(MGraph *G)/* 构件图 */ { int i, j; /* printf("请输入边数和顶点数:"); */ G->numEdges=13; G->numVertexes=10; for (i = 0; i < G->numVertexes; i++)/* 初始化图 */ { G->vexs[i]=i; } for (i = 0; i < G->numVertexes; i++)/* 初始化图 */ { for ( j = 0; j < G->numVertexes; j++) { if (i==j) G->arc[i][j]=0; else G->arc[i][j]=INFINITYC; } } G->arc[0][1]=3; G->arc[0][2]=4; G->arc[1][3]=5; G->arc[1][4]=6; G->arc[2][3]=8; G->arc[2][5]=7; G->arc[3][4]=3; G->arc[4][6]=9; G->arc[4][7]=4; G->arc[5][7]=6; G->arc[6][9]=2; G->arc[7][8]=5; G->arc[8][9]=3; } //2.将邻近矩阵转化成邻接表 void CreateALGraph(MGraph G,GraphAdjList *GL){ int i,j; EdgeNode *e; *GL = (GraphAdjList)malloc(sizeof(graphAdjList)); (*GL)->numVertexes=G.numVertexes; (*GL)->numEdges=G.numEdges; //读入顶点信息,建立顶点表 for(i= 0;i <G.numVertexes;i++) { (*GL)->adjList[i].in=0; (*GL)->adjList[i].data=G.vexs[i]; //将边表置为空表 (*GL)->adjList[i].firstedge=NULL; } //建立边表 for(i=0;i<G.numVertexes;i++) { for(j=0;j<G.numVertexes;j++) { if (G.arc[i][j]!=0 && G.arc[i][j]<INFINITYC) { e=(EdgeNode *)malloc(sizeof(EdgeNode)); //邻接序号为j e->adjvex=j; e->weight=G.arc[i][j]; //将当前顶点上的指向的结点指针赋值给e e->next=(*GL)->adjList[i].firstedge; //将当前顶点的指针指向e (*GL)->adjList[i].firstedge=e; (*GL)->adjList[j].in++; } } } }/* 关于AOE网图的存储代码段-End! */ int *etv,*ltv; /* 事件最早发生时间和最迟发生时间数组,全局变量 */ int *stack2; /* 用于存储拓扑序列的栈 */ int top2; /* 用于stack2的指针*/ //拓扑排序 Status TopologicalSort(GraphAdjList GL){ //若GL无回路,则输出拓扑排序序列且返回状态OK, 否则返回状态ERROR; EdgeNode *e; int i,k,gettop; //栈指针下标; int top = 0; //用于统计输出的顶点个数.作为拓扑排序是否存在回路的判断依据; int count = 0; //建栈,将入度in = 0的顶点入栈; int *stack = (int *)malloc(GL->numVertexes * sizeof(int)); //遍历顶点表上入度in = 0 入栈 for (i = 0; i < GL->numVertexes;i++) { //printf("%d %d\n",i,GL->adjList[i].in); if ( 0 == GL->adjList[i].in ) { stack[++top] = i; } } //* stack2 的栈指针下标 top2 = 0; //* 初始化拓扑序列栈 stack2 = (int *)malloc(sizeof(int) * GL->numVertexes); //* 事件最早发生时间数组 etv = (int *)malloc(sizeof(GL->numVertexes * sizeof(int))); //* 初始化etv 数组 for (i = 0 ; i < GL->numVertexes; i++) { //初始化 etv[i] = 0; } printf("TopologicSort:\t"); while (top != 0) { gettop = stack[top--]; printf("%d -> ", GL->adjList[gettop].data); count++; //将弹出的顶点序号压入拓扑排序的栈中; stack2[++top2] = gettop; //例如gettop为V0 ,那么与V0相连接的结点就有etv[1] = 3; etv[2] = 4; //例如gettop为V1 ,那么与V1连接的结点就有etv[4]= 3+6=9; etv[3] = 8; //例如gettop为V2 ,那么与V2连接的结点就有etv[5]= 4+7=11; etv[3] = 12; //例如gettop为V3 ,那么与V3连接的结点就有etv[4]= 12+3=15; for(e = GL->adjList[gettop].firstedge; e; e = e->next) { k = e->adjvex; //将i顶点连接的邻接顶点入度减1,如果入度减一后为0,则入栈 if(!(--GL->adjList[k].in)) stack[++top] = k; //求各顶点事件的最早发生的时间etv值 //printf("etv[gettop]+e->weight = %d\n",etv[gettop]+e->weight); //printf("etv[%d] = %d\n",k,etv[k]); if ((etv[gettop] + e->weight) > etv[k]) { etv[k] = etv[gettop] + e->weight; } } } printf("\n"); //打印etv(事件最早发生时间数组) // for (i = 0; i < GL->numVertexes; i++) { // printf("etv[%d] = %d\n",i,etv[i]); // } // printf("\n"); if(count < GL->numVertexes) return ERROR; else return OK; return OK; } //求关键路径, GL为有向网,则输出G的各项关键活动; void CriticalPath(GraphAdjList GL){ EdgeNode *e; int i,gettop,k,j; //声明活动最早发生时间和最迟发生时间变量; int ete,lte; //求得拓扑序列,计算etv数组以及stack2的值 TopologicalSort(GL); //打印etv数组(事件最早发生时间) printf("etv:\n"); for(i = 0; i < GL->numVertexes; i++) printf("etv[%d] = %d \n",i,etv[i]); printf("\n"); //事件最晚发生时间数组 ltv = (int *)malloc(sizeof(int) * GL->numVertexes); //初始化ltv数组 for (i = 0; i < GL->numVertexes; i++) { //初始化ltv数组. 赋值etv最后一个事件的值 ltv[i] = etv[GL->numVertexes-1]; //printf("ltv[%d] = %d\n",i,ltv[i]); } //计算ltv(事件最晚发生时间) 出栈求ltv while (top2 != 0) { //出栈(栈顶元素) gettop = stack2[top2--]; //找到与栈顶元素连接的顶点; 例如V0是与V1和V2连接 for (e = GL->adjList[gettop].firstedge; e; e = e->next) { //获取与gettop 相连接的顶点 k = e->adjvex; //计算min(ltv[k]-e->weight,ltv[gettop]) if (ltv[k] - e->weight < ltv[gettop]) { //更新ltv 数组 ltv[gettop] = ltv[k] - e->weight; } } } //打印ltv 数组 printf("ltv:\n"); for (i = 0 ; i < GL->numVertexes; i++) { printf("ltv[%d] = %d \n",i,ltv[i]); } printf("\n"); //求解ete,lte 并且判断lte与ete 是否相等.相等则是关键活动; //2层循环(遍历顶点表,边表) for(j=0; j<GL->numVertexes;j++) { for (e = GL->adjList[j].firstedge; e; e = e->next) { //获取与j连接的顶点; k = e->adjvex; //ete 就是表示活动 <Vk, Vj> 的最早开工时间, 是针对这条弧来说的.而这条弧的弧尾顶点Vk 的事件发生了, 它才可以发生. 因此ete = etv[k]; ete = etv[j]; //lte 表示活动<Vk, Vj> 的最晚开工时间, 但此活动再晚也不能等Vj 事件发生才开始,而是必须在Vj 事件之前发生. 所以lte = ltv[j] - len<Vk, Vj>. lte = ltv[k]-e->weight; //如果ete == lte 则输出j,k以及权值; if (ete == lte) { printf("<%d-%d> length:%d\n",GL->adjList[j].data, GL->adjList[k].data, e->weight); } } } }