图的应用
三、拓扑排序
在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,称为AOV网,AOV网中不能存在回路。
设G=(V,E)是一个具有n个顶点的有向图,V中的顶点序列v1,v2,......,vn,满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前,则我们称这样的顶点序列为一个拓扑序列。
注意:拓扑序列并不是唯一的
拓扑排序即是对一个有向图构造拓扑序列的过程。构造时,若顶点全部输出则不存咋回路,若少顶点,则存在回路,即不构成AOV网
拓扑排序基本思想:
从AOV网中选择一个入度为0的顶点输出,然后删除此顶点和以此顶点为弧尾的弧,继续重复此步骤,直至全部输出顶点或者AOV网中不存在入度为0的顶点为止,在判断输出顶点数是否就是全部顶点数。
AOV网在邻接表的基础上给顶点表结点增加了一个入度域in,如下
in | data | firstedge |
对如下邻接表进行拓扑排序
各种结点的结构代码
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;
拓扑排序(利用栈)
//拓扑排序,若图GL无回路,则输出拓扑排序并返回ok,否则返回error
Status TopologicalSort(GraphAdjList GL)
{
EdgeNode *e;
int i,k,gettop;
int top = 0; //用于指向栈顶
int count = 0; //统计输出顶点个数
int *stack; //建立栈,存储入度为0的顶点
stack = (int *)malloc(GL->numVertexes * sizeof(int));
for(i=0; i<GL->numVertexes; i++) //遍历顶点
if(GL->adjList[i].in == 0)
stack[++top] = i; //将入度为0的顶点入栈
while(top!=0) //栈不为空
{
gettop = stack[top--]; //栈顶的顶点下标出栈
cout<<GL->adjList[gettop].data>>" ->"; //打印此顶点
count++; //统计输出顶点
for(e=GL->adjList[gettop].firstedge;e;e=e->next)
//对该顶点的弧表进行遍历
{
k=e->adjvex; //k为原顶点的邻接顶点
if(!(--GL->adjList[k].in)) //该邻接顶点k的入度减一
stack[++top] = k; //若顶点k的入度为0,则入栈
}
}
if(count<GL->numVertexes) //输出顶点数小于全部顶点数
return error;
else
return ok;
}
栈的初始化复杂度为O(n),while循环中进栈、出栈、入度减1的操作共执行了e次,所以整个算法的时间复杂度为O(n+e)
四、关键路径
在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网,我们称为AOE网
AOE网中,没有入边的顶点称为始点或者源点,没有出边的顶点称为终点或者汇点,一般来说,AOE网只有一个源点和终点。
路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。
定义参数:
1.事件的最早发生时间etv:即顶点vk的最早发生时间
2.事件的最晚发生时间ltv:即顶点vk的最晚发生时间
3.活动的最早开工时间ete:即弧ak的最早发生时间
4.活动的最晚开工时间lte:即弧ak的最晚发生时间
可以根据1、2求3、4,然后判断ete[k]与lte[k]是不是相等就可以判断其是否为关键活动
例子:对如下图求关键路径
在求关键路径之前,需要进行拓扑排序,得到etv和拓扑序列列表
int *etv,*ltv; //存储顶点发生的最早时间和最晚的时间
int *stack2; //存储拓扑序列的栈
//拓扑排序,若图GL无回路,则输出拓扑排序并返回ok,否则返回error
Status TopologicalSort(GraphAdjList GL)
{
EdgeNode *e;
int i,k,gettop;
int top = 0; //用于指向栈顶
int count = 0; //统计输出顶点个数
int *stack; //建立栈,存储入度为0的顶点
stack = (int *)malloc(GL->numVertexes * sizeof(int));
for(i=0; i<GL->numVertexes; i++) //遍历顶点
if(GL->adjList[i].in == 0)
stack[++top] = i; //将入度为0的顶点入栈
top2 = 0; //初始化栈2指针
//事件最早发生数组创建和初始化
etv = (int *)malloc(GL->numVertexes * sizeof(int));
for(i=0; i<GL->numVertexes; i++)
etv[i] = 0;
//栈2初始化
stack2 = (int *)malloc(GL->numVertexes * sizeof(int));
while(top!=0) //栈不为空
{
gettop = stack[top--]; //栈顶的顶点下标出栈
stack2[++top] = gettop; //将弹出顶点艳茹拓扑序列栈
count++; //统计输出顶点
for(e=GL->adjList[gettop].firstedge;e;e=e->next)
//对该顶点的弧表进行遍历
{
k=e->adjvex; //k为原顶点的邻接顶点
if(!(--GL->adjList[k].in)) //该邻接顶点k的入度减一
stack[++top] = k; //若顶点k的入度为0,则入栈
//比较各个邻接顶点发生的的最早事件,将各自最长的存入etv
//类似于最短路径,不过这里求的是最长
if(etv[gettop] + e->weight > etv[k])
etv[k] = etv[gettop] + e->weight
}
}
if(count<GL->numVertexes) //输出顶点数小于全部顶点数
return error;
else
return ok;
}
上述代码可见,求etv[k]的最早发生公式为
关键路径的算法代码
//求关键路径,GL为有向网,输出GL的各项关键活动
void CriticalPath(GraphAdjList GL)
{
EdgeNode *e;
int i,gettop,k,j;
int ete,lte; //活动最早和最迟发生的时间变量
TopologicalSort(GL); //求拓扑排序序列,计算数组etv和stack2的值
ltv = (int *)malloc(GL->numVertexes * sizeof(int)); //申请空间
for(i=0;i<GL->numVertexes;i++) //初始化ltv
while(top2 != 0)
{
gettop = stack2[top--]; //拓扑序列出栈
for(e=GL->adjList[gettop].firstedge;e;e=e->next)
//依次遍历该顶点的所有邻接顶点
{
k = e->adjvex; //k为邻接顶点下标
if(ltv[k]-e->weight<ltv[gettop]) //求各顶点发生的最晚时间
{
ltv[gettop] = ltv[k]-e->weight;
}
}
}
for(j=0;j<GL->numVertexes;j++) //循环所有顶点
{
//循环所有顶点的邻接顶点
for(e=GL->adjList[j].firstedge;e;e = e->next)
{
k = e ->adjvex;
ete = etv[j]; //活动最早时间
lte = ltv[k] - e->weight; //活动最迟发生时间
if(ete ==lte) //相等的弧即在关键路径上
printf("<v%d , v%d>length:%d,",
GL->adjList[j].data,GL->adjList[k].data,e->weigeht);
}
}
}
上述代码中,在求ltv时可以发现,其实即是吧拓扑排序倒过来进行,计算公式如下
得到etv和ltv如下
上图可得的信息:
从v0到v9最大距离为27,而etv计算的即是每个点到v0的最大距离
而ltv = 用最大距离27 - 该顶点到v9的最大距离
(在代码计算中,先用ltv[k]-e->weight当做ltv[gettop]值,然后不断遍历其他的邻接点,更新找到最小的那个(即gettop-9距离最大))。
etv[1] = 3,ltv[1] = 7表示v1事件最早能在第三天开始,最迟也要在第7天开始
而ete表示弧<vk,vj>的最早开工时间,即ete = etv[k]
lte表示弧<vk,vj>的最晚开工时间,即lte = ltv[j]-len<vk,vj>
上述求关键路径的代码,时间复杂度为O(n+e);