标题:拓扑排序之关键路径
只看楼主
巧若拙
Rank: 4
来 自:宁波余姚
等 级:业余侠客
威 望:1
帖 子:159
专家分:273
注 册:2014-8-24
结帖率:46.15%
 问题点数:0 回复次数:5 
拓扑排序之关键路径
/*
    Name: 拓扑排序之关键路径
    Copyright:
    Author: 巧若拙
    Date: 17-11-14 21:02
    Description: 拓扑排序之关键路径
    若在带权的有向图中,以顶点表示事件,以有向边表示活动,边上的权值表示活动的开销(如该活动持续时间),
则此带权的有向图称为边表示活动的网 (Activity on Edge Network) ,简称 AOE 网。
(1)AOV 网具有的性质
⒈ 只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始。
⒉ 只有在进入某一顶点的各有向边所代表的活动都已经结束,该顶点所代表的事件才能发生。
⒊ 表示实际工程计划的 AOE 网应该是无环的,并且存在唯一的入度过为 0 的开始顶点和唯一的出度为 0 的完成顶点。
(2)由事件 v j 的最早发生时间和最晚发生时间的定义 , 可以采取如下步骤求得关键活动 :
1. 从开始顶点 v 1 出发 , 令 ve(1)=0, 按拓朴有序序列求其余各顶点的可能最早发生时间。
Ve(k)=max{ve(j)+dut(<j,k>)} ( 7.1 )
j ∈ T
其中 T 是以顶点 v k 为尾的所有弧的头顶点的集合 (2 ≤ k ≤ n) 。
如果得到的拓朴有序序列中顶点的个数小于网中顶点个数 n ,则说明网中有环,不能求出关键路径,算法结束。
2. 从完成顶点 v n 出发,令 vl(n)=ve(n) ,按逆拓朴序列求其余各顶点的允许的最晚发生时间 :
vl(j)=min{vl(k)-dut(<j,k>)}
k ∈ S
其中 S 是以顶点 v j 是头的所有弧的尾顶点集合 (1 ≤ j ≤ n-1) 。
3. 求每一项活动 a i (1 ≤ i ≤ m) 的最早开始时间 e(i)=ve(j) ;最晚开始时间
l(i)=vl(k)-dut(<j,k>)
。若某条弧满足 e(i)=l(i) ,则它是关键活动。

输入:
第一行两个整数n,m分别表示顶点个数和边的条数,其中顶点的编号为0~n-1。
接下来的m行,每行有三个数u,v,w,表示从弧尾u到弧头v的边的权值w。
10 13
0 1 3
0 2 4
1 3 5
1 4 6
2 3 8
2 5 7
3 4 3
4 6 9
4 7 4
5 7 6
6 9 2
7 8 5
8 9 3

输出:
输出一条关键路径,格式如下:0->2->3->4->7->8->9

算法分析:
采用广度优先搜索进行拓扑排序,获取拓扑序列的同时计算各顶点事件的最早发生时间,然后逆序计算各顶点事件的最晚发生时间。
本文是《大话数据结构》的读书笔记,但算法实现与《大话数据结构》完全不同,自我感觉比书上的算法要简洁,呵呵!
*/
#include<stdio.h>
#include<stdlib.h>

#define MAXN 26   //最大顶点数量
#define MAXM 100000   //最大边数量

typedef char VertexType; //顶点类型由用户自定义
typedef int EdgeType; //边上的权值类型由用户自定义

typedef struct Edge{ //边集数组
    int u, v; //弧尾和弧头
    int next; //指向同一个弧尾的下一条边
    EdgeType w; //权值,对于非网图可以不需要
} EdgeLib;

void PrintGraph(int first[], EdgeLib edge[], int n);//输出图
int TopoLogicalSort(int topo[], int Etv[], EdgeLib edge[], int In[], int first[], int n);
void CriticalPath(EdgeLib edge[], int In[], int first[], int n);//求关键路径

int main()
{
    int i, m, n;
    int In[MAXN], first[MAXN]; //存储顶点信息
    EdgeLib edge[MAXM]; //存储边信息
   
    for (i=0; i<MAXN; i++)//初始化图
    {
        first[i] = -1;
        In[i] = 0;
    }
   
    printf("请输入顶点数量和边数量:");
    scanf("%d%d", &n, &m);   
    for (i=0; i<m; i++)
    {
        scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].w);
        edge[i].next = first[edge[i].u];
        first[edge[i].u] = i;
        In[edge[i].v]++;
    }
   
    CriticalPath(edge, In, first, n);//求关键路径
   
    return 0;
}

void PrintGraph(int first[], EdgeLib edge[], int n)//输出图
{
    int i, j;
   
    for (i=0; i<n; i++)
    {
        printf("G[%d] = %c: ", i, i+'a');
        j = first[i]; //指向i的第一条边
        while (j != -1)
        {
            printf("<%c, %c>, ", edge[j].u+'a', edge[j].v+'a');
            j = edge[j].next; //指向下一条边
        }
        printf("\n");
    }
    printf("\n");
}

int TopoLogicalSort(int topo[], int Etv[], EdgeLib edge[], int In[], int first[], int n)
{
    int i, k, front, rear;
   
    front = rear = 0;
    for (i=0; i<n; i++)//将入度为0的顶点入队列
    {
        Etv[i] = 0; //初始化各事件最早发生事件为0
        if (In[i] == 0)
        {
            topo[rear++] = i;
        }
    }
   
    while (front < rear)//采用广度优先搜索获取拓扑序列
    {
        k = topo[front++];
        for (i=first[k]; i!=-1; i=edge[i].next)
        {
            if (--In[edge[i].v] == 0)
                topo[rear++] = edge[i].v;
            
            if (Etv[edge[i].v] < Etv[edge[i].u] + edge[i].w)//更新各顶点事件的最早发生时间
                Etv[edge[i].v] = Etv[edge[i].u] + edge[i].w;
        }
    }
   
    return (rear == n);//如果count小于顶点数,说明存在环
}

void CriticalPath(EdgeLib edge[], int In[], int first[], int n)//求关键路径
{
    int i, j;
    int topo[MAXN];
    int Etv[MAXN], Ltv[MAXN];//存储事件的最早和最晚发生时间
   
    if (!TopoLogicalSort(topo, Etv, edge, In, first, n))
    {
        puts("不存在关键路径");     
        return;
    }
   
    for (i=0; i<n; i++)
    {
        Ltv[i] = Etv[n-1]; //初始化各事件最晚发生事件为最后一个事件发生的时间
    }
   
    for (j=n-2; j>=0; j--)
    {
        for (i=first[topo[j]]; i!=-1; i=edge[i].next)
        {
            if (Ltv[edge[i].u] > Ltv[edge[i].v] - edge[i].w)//更新各顶点事件的最晚发生时间
                Ltv[edge[i].u] = Ltv[edge[i].v] - edge[i].w;
        }
    }
   
    printf("%d", topo[0]); //输出关键路径
    for (i=1; i<n; i++)
    {
        if (Etv[topo[i]] == Ltv[topo[i]])
        {
            printf("->%d", topo[i]);
        }
    }
    printf("\n");
}
搜索更多相关主题的帖子: Copyright Network 
2014-11-17 23:08
巧若拙
Rank: 4
来 自:宁波余姚
等 级:业余侠客
威 望:1
帖 子:159
专家分:273
注 册:2014-8-24
得分:0 
不好意思,上面的程序中输出关键路径的代码有误,当出现多个关键事件顶点或多条关键路径时,不能正确输出。
现修改如下:
void CriticalPath(EdgeLib edge[], int In[], int first[], int n)//求关键路径
{
    int i, j;
    int topo[MAXN], path[MAXN];
    int Etv[MAXN], Ltv[MAXN];//存储事件的最早和最晚发生时间
   
    if (!TopoLogicalSort(topo, Etv, edge, In, first, n))
    {
        puts("不存在关键路径");     
        return;
    }
   
    for (i=0; i<n; i++)
    {
        Ltv[i] = Etv[n-1]; //初始化各事件最晚发生事件为最后一个事件发生的时间
    }
   
    for (j=n-2; j>=0; j--)
    {
        for (i=first[topo[j]]; i!=-1; i=edge[i].next)
        {
            if (Ltv[edge[i].u] > Ltv[edge[i].v] - edge[i].w)//更新各顶点事件的最晚发生时间
                Ltv[edge[i].u] = Ltv[edge[i].v] - edge[i].w;
        }
    }
   
    path[0] = topo[0];
    PrintPath(edge, first, Etv, Ltv, path, 1, topo[n-1]);
}

void PrintPath(EdgeLib edge[], int first[], int Etv[], int Ltv[], int path[], int top, int end)//深度优先搜索输出关键路径
{
    int i, u = path[top-1];

    if (u == end)
    {
        printf("%d", path[0]); //输出关键路径
        for (i=1; i<top; i++)
        {
            printf("->%d", path[i]);
        }
        printf("\n");
        return;
    }
   
    for (i=first[u]; i!=-1; i=edge[i].next)
    {
        if (Etv[edge[i].v] == Ltv[edge[i].v])//关键事件
        {
            path[top++] = edge[i].v; //入栈
            PrintPath(edge, first, Etv, Ltv, path, top, end);
            top--; //退栈
        }
    }
}


可用以下数据测试:
8 9
0 1 5
0 2 4
1 4 3
2 3 2
3 4 2
4 5 6
4 6 6
5 7 2
6 7 2

输出:
输出所有关键路径,每行输出一个关键路径。格式如下:
0->2->3->4->5->7
0->2->3->4->6->7
0->1->4->5->7
0->1->4->6->7
2014-11-18 16:26
巧若拙
Rank: 4
来 自:宁波余姚
等 级:业余侠客
威 望:1
帖 子:159
专家分:273
注 册:2014-8-24
得分:0 
再补充一个深度优先搜索的算法:
/*
    Name: 拓扑排序之关键路径
    Copyright:
    Author: 巧若拙
    Date: 17-11-14 21:02
    Description: 拓扑排序之关键路径
    若在带权的有向图中,以顶点表示事件,以有向边表示活动,边上的权值表示活动的开销(如该活动持续时间),
则此带权的有向图称为边表示活动的网 (Activity on Edge Network) ,简称 AOE 网。
(1)AOV 网具有的性质
⒈ 只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始。
⒉ 只有在进入某一顶点的各有向边所代表的活动都已经结束,该顶点所代表的事件才能发生。
⒊ 表示实际工程计划的 AOE 网应该是无环的,并且存在唯一的入度过为 0 的开始顶点和唯一的出度为 0 的完成顶点。
(2)由事件 v j 的最早发生时间和最晚发生时间的定义 , 可以采取如下步骤求得关键活动 :
1. 从开始顶点 v 1 出发 , 令 ve(1)=0, 按拓朴有序序列求其余各顶点的可能最早发生时间。
Ve(k)=max{ve(j)+dut(<j,k>)} ( 7.1 )
j ∈ T
其中 T 是以顶点 v k 为尾的所有弧的头顶点的集合 (2 ≤ k ≤ n) 。
如果得到的拓朴有序序列中顶点的个数小于网中顶点个数 n ,则说明网中有环,不能求出关键路径,算法结束。
2. 从完成顶点 v n 出发,令 vl(n)=ve(n) ,按逆拓朴序列求其余各顶点的允许的最晚发生时间 :
vl(j)=min{vl(k)-dut(<j,k>)}
k ∈ S
其中 S 是以顶点 v j 是头的所有弧的尾顶点集合 (1 ≤ j ≤ n-1) 。
3. 求每一项活动 a i (1 ≤ i ≤ m) 的最早开始时间 e(i)=ve(j) ;最晚开始时间
l(i)=vl(k)-dut(<j,k>)
。若某条弧满足 e(i)=l(i) ,则它是关键活动。

输入:
第一行两个整数n,m分别表示顶点个数和边的条数,其中顶点的编号为0~n-1。
接下来的m行,每行有三个数u,v,w,表示从弧尾u到弧头v的边的权值w。
8 10
0 1 5
0 2 4
1 4 3
2 3 2
3 4 1
4 5 6
4 6 6
5 7 2
6 7 2
4 7 8

输出:
输出所有关键路径,每行输出一个关键路径。格式如下:
0->1->4->7
0->1->4->5->7
0->1->4->6->7

算法分析:
采用深度优先搜索进行拓扑排序,获取拓扑序列的同时计算各顶点事件的最早发生时间,然后逆序计算各顶点事件的最晚发生时间。
本文是《大话数据结构》的读书笔记,在输出关键路径时采用深度优先搜索输出关键路径,能输出多条关键路径。
*/
#include<stdio.h>
#include<stdlib.h>

#define MAXN 26   //最大顶点数量
#define MAXM 100000   //最大边数量

typedef int VertexType; //顶点类型由用户自定义
typedef int EdgeType; //边上的权值类型由用户自定义

typedef struct EdgeNode{ //边表结点
    int adjvex;  //邻接点域,存储该顶点对应的下标
    EdgeType weight; //权值,对于非网图可以不需要
    struct EdgeNode *next; //链域,指向下一个邻接点
} EdgeNode;

typedef struct VertexNode{ //顶点表结点
    VertexType data; //顶点域,存储顶点信息
    int in;   //存储顶点入度的数量
    EdgeNode *firstEdge; //边表头指针
} VertexNode;

void CreateGraph(VertexNode *GL, int n, int m);//把顶点和边信息读入到表示图的邻接表中
int TopoLogicalSort_DFS(int topo[], int Etv[], VertexNode *GL, int n);//深度优先搜索获取拓扑序列
void CriticalPath(VertexNode *GL, int n);//求关键路径
void PrintPath(VertexNode *GL, int Etv[], int Ltv[], int path[], int top, int end);//深度优先搜索输出关键路径

int main()
{
    int i, m, n;
    VertexNode GL[MAXN];
   
    printf("请输入顶点数量和边数量:");
    scanf("%d%d", &n, &m);   
    CreateGraph(GL, n, m);//把顶点和边信息读入到表示图的邻接表中
    CriticalPath(GL, n);//求关键路径
   
    return 0;
}

void CreateGraph(VertexNode *GL, int n, int m)//把顶点和边信息读入到表示图的邻接表中
{
    int i, u, v;
    EdgeNode *e;

    for (i=0; i<n; i++)//初始化图
    {
        GL[i].data = i;
        GL[i].in = 0;
        GL[i].firstEdge = NULL;
    }
   
    for (i=0; i<m; i++)
    {
        e = (EdgeNode*)malloc(sizeof(EdgeNode)); //采用头插法插入边表结点
        if (!e)
        {
            puts("Error");
            exit(1);
        }
        
        scanf("%d%d%d", &u, &v, &e->weight);
        e->next = GL[u].firstEdge;
        GL[u].firstEdge = e;
        e->adjvex = v;
        GL[v].in++;
    }
}

int TopoLogicalSort_DFS(int topo[], int Etv[], VertexNode *GL, int n)//深度优先搜索获取拓扑序列
{
    int i, u, v, top, count = 0;
    EdgeNode *e;
    int *Stack;
   
    Stack = (int*)malloc(sizeof(int) * n); //分配栈空间
    if (!Stack)
    {
        puts("Error");
        exit(1);
    }
    for (top=i=0; i<n; i++)//将入度为0的顶点入栈
    {
        Etv[i] = 0; //初始化各事件最早发生事件为0
        if (GL[i].in == 0)
        {
            Stack[top++] = i;
        }
    }
   
    while (top > 0)//采用深度优先搜索获取拓扑序列
    {
        u = Stack[--top];
        topo[count++] = u;
        
        for (e=GL[u].firstEdge; e!=NULL; e=e->next)//将u的邻接点入度减1,并将入度为0的顶点入栈
        {
            v = e->adjvex;
            if (--GL[v].in == 0)
                Stack[top++] = v;
               
            if (Etv[v] < Etv[u] + e->weight)//更新各顶点事件的最早发生时间
                Etv[v] = Etv[u] + e->weight;
        }
    }
   
    free(Stack);
   
    return (count == n);//如果count小于顶点数,说明存在环
}

void CriticalPath(VertexNode *GL, int n)//求关键路径
{
    int i, u, v;
    EdgeNode *e;
    int topo[MAXN], path[MAXN];
    int Etv[MAXN], Ltv[MAXN];//存储事件的最早和最晚发生时间
   
    if (!TopoLogicalSort_DFS(topo, Etv, GL, n))
    {
        puts("不存在关键路径");     
        return;
    }
   
    for (i=0; i<n; i++)
    {
        Ltv[i] = Etv[n-1]; //初始化各事件最晚发生事件为最后一个事件发生的时间
    }
   
    for (i=n-2; i>=0; i--)
    {
        u = topo[i];
        for (e=GL[u].firstEdge; e!=NULL; e=e->next)
        {
            v = e->adjvex;
            if (Ltv[u] > Ltv[v] - e->weight)//更新各顶点事件的最晚发生时间
                Ltv[u] = Ltv[v] - e->weight;
        }
    }
   
    path[0] = topo[0];
    PrintPath(GL, Etv, Ltv, path, 1, topo[n-1]);
}

void PrintPath(VertexNode *GL, int Etv[], int Ltv[], int path[], int top, int end)//深度优先搜索输出关键路径
{
    int i, u = path[top-1];
    EdgeNode *e;

    if (u == end)
    {
        printf("%d", path[0]); //输出关键路径
        for (i=1; i<top; i++)
        {
            printf("->%d", path[i]);
        }
        printf("\n");
        return;
    }
   
    for (e=GL[u].firstEdge; e!=NULL; e=e->next)
    {
        if (Etv[e->adjvex] == Ltv[e->adjvex])//关键事件
        {
            path[top++] = e->adjvex; //入栈
            PrintPath(GL, Etv, Ltv, path, top, end);
            top--; //退栈
        }
    }
}
2014-11-18 22:02
HilaryZH
Rank: 1
等 级:新手上路
帖 子:2
专家分:0
注 册:2015-6-12
得分:0 
非常感谢
2015-06-12 15:56
赵董
Rank: 1
来 自:黑龙江八一农垦大学
等 级:新手上路
帖 子:2
专家分:0
注 册:2017-6-6
得分:0 
回复 楼主 巧若拙
关键路径中的frist是什么意思啊,谢谢

好好学习,天天向上
2017-06-15 10:07
赵董
Rank: 1
来 自:黑龙江八一农垦大学
等 级:新手上路
帖 子:2
专家分:0
注 册:2017-6-6
得分:0 
回复 楼主 巧若拙
楼主 关键路径里的first是什么意思呀 谢谢啦

好好学习,天天向上
2017-06-15 10:52



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-438769-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.307594 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved