数据结构 -- 图的应用(二)
图的应用
有向无环图(DAG)
有向无环图:若一个有向图中不存在环,则称为有向无环图,简称DAG图
DAG描述表达式
方法:
利用规律:最终有向无环图中,定点中不会出现重复的操作数
step1:把各个操作数不重复地排成一排
step2:标出各个运算符生效的顺序(先后顺序有点出入无所谓)
step3:按顺序加入运算符,注意分层
step4:自底向上逐层检查同层地运算符是否合体
使用有向无环图描述表达式结果不唯一
AOV网
AOV网用定点表示活动的网
用DAG图(有向无环图)表示一个工程。顶点表示活动,有向边<vi,vj>表示活动vi必须先于活动vj进行
拓扑排序
找到要做事的先后顺序
实现:
① 从AOV网中选择一个没有前驱(入度为0)的顶点并输出
② 从网中删除该顶点和所有以它为起点的有向边
③ 重复①和②直到当前的AOV为空或当前网中不存在无前驱的顶点为止(说明存在回路)
#define MaxVertexNum 100
typedef struct AcrNode{ //边表结点int adjvex;struct ArcNode *nextarc; //指向下一条弧的指针//InfoType info //网的边权值
}ArcNode;
typedef struct VNode{ //顶点表结点VertexType data; //顶点信息ArcNode *firstarc; //指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct{AdjList wertices; //邻接表int vexnum,arcnum; //图的顶点数和弧数}Graph;
bool TopologicalSrot(Graph G){initStack(S);for(int i = 0;i<G.vexnum;i++){if(indegree[i] == 0)Push(s,i); //将所有入度为0的顶点进栈}int count = 0; //计数,记录当前已经输出的顶点数while(!isEmpty(S)){ //栈不空,则存在入度为0的顶点Pop(S,i); //栈顶元素出栈print[count++]=i; //输出顶点ifor(p=G.vertices[i].firstarc;p;p=p->nextarc){//将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈s中v = p->adjvex;if(!(--indegree[v]))Push(S,v); //入度为0,则入栈}}//whileif(count<G.vexnum)return false; //排序失败,说明有向图中有回路elsereturn true; //拓扑排序成功
}
逆拓扑排序
对于一个AOV网,如果采用下列步骤进行排序,则称之为逆拓扑排序:
① 从AOV网中选择一个没有后继(出度为0)的顶点并输出
② 从网中删除该顶点和所有以它为起点的有向边
③ 重复①和②直到当前的AOV为空
【思考】使用不同的存储结构对时间复杂度的影响
如果使用邻接表进行存储,要对所有边进行遍历才能找到顶点的出度,时间复杂度高(低效)
邻接矩阵更方便些
【补充】
逆邻接表:每个顶点连接的顶点是指向当前顶点的顶点
逆拓扑排序的实现(DFS算法)
void DFSTraverse(Graph G){ //对图G进行深度优先遍历for(v = 0;v<G.vexnum;++v)visited[v] = FALSE; //初始化已访问标记数据for(v = 0;v<G.vexnum;++v) //本代码中是从v=0开始遍历if(!visited[v])DFS(G.v);
}
void DFS(Graph G,int v){ //从顶点v出发,深度优先遍历图Gvisit(v); //访问顶点vvisited[v] = TRUE; //设已访问标记for(w = FirstNeighbor(G,v);w>=0;w = NextNeighbor(G,v,w))if(!visited[w]){ //w为u的尚未访问的邻接顶点DFS(G,w);} //if
}
DFS实现逆拓扑排序:在顶点退栈前输出
AOE网
在带权有向图中,以顶点表示事件,以有点改变表示活动,以边上的权值表示完成该活动的开销(如完成活动所需的时间),称之为用边表示活动的网络,简称AOE网
性质:
① 只有在某顶点所代表的事件发生后,从该顶点出发阿德各有向边所代表的活动才能开始
② 只有在进入某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才能发生
另外,有些活动是可以并发进行的
关键路径
在AOE网中仅有一个入度为0的顶点,称为开始顶点(源点),在所有路径中,具有最大路径长度称为关键路径,而把关键路径上的活动称为关键活动
完成整个工程的最短时间就是关键路径的长度,若关键活动不能按时完成,则整个工程的完成时间就会延长
事件vk的最早发生时间ve(k) – 决定了所有从vk开始的活动能够开工的最早时间
活动ai最早开始时间e(i)) – 指该活动弧的起点所表示的事件的最早发生时间
事件vk的最是发生时间vl(k) – 指在不推迟整个过程完成的前提下,该时间最迟必须发生时间
活动ai的最迟开始时间l(i) – 指该活动弧的重点所表示事件的最迟发生时间与该活动所需时间之差
时间余量为零的活动为关键活动
由关键活动组成的路径就是关键路径
求关键路径的步骤
① 求各个事件的最早发生时间
按照拓扑排序序列,依次求各个顶点的ve(k)
② 求所有事件的最迟发生时间
按逆拓扑排序,依次求各个顶点的vl(k)
③ 求所有活动的最早发生时间
④求所有活动的最迟发生时间
⑤ 求所有活动的时间余量
关键活动、关键路径的特性
若关键活动耗时增加,则整个工程的工期将增长
缩短关键活动的时间,可以缩短整个工程的工期
当缩短到一定程度,关键路径可能会变成非关键活动