图的拓扑排序与关键路径

拓扑排序与关键路径是有向无环图上的应用。两种算法使用同一种动态规划的思想,因此关键路径的代码几乎和拓扑排序完全一样

(一)拓扑排序

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

拓扑排序的目的是得到一个满足条件的序列,这样的序列可能有多种。如下图拓扑序列就有

C1 , C2 , C3 , C4 , C5 , C6 , C8 , C9 , C7 或      C1 , C8 , C9 , C2 , C5 , C3 , C4 , C7 , C6

另外,存储结构差异(邻接矩阵和邻接表)也可能导致序列不同。

算法实现:要想输出上图C4,必须先输出其前驱结点C2和C3,这实际上是一种动态规划思想。此处采用递推实现,例如输出C2时,将这一信息传递给C2的邻接点C3和C5。为了完成信息传递,使用一个数组d来存储每个点的入度,当结点X的前驱结点Y输出时,将X的入度减一,如果入度减一后为0,那么结点X所有前驱都已经输出,此时X可以输出。通常使用队列(也可以用其他查找结构)来存储这些度为0的结点。下为18734 拓扑排序代码。

#include <iostream>
#include <queue>
using namespace std;
int n,m,e[105][105],d[105];
int main()
{
    int i,j,x,y,z;
    cin>>n>>m;
    for(i=1;i<=m;i++)
    {
        cin>>x>>y;
        e[x][y]=1;
        d[y]++;/**< 统计入度 */
    }
    //int q[1005],f=0,r=0;
    priority_queue<int,vector<int>,greater<int> > q;
    for(i=1;i<=n;i++)/**< 题目要求拓扑序列字典序最小,所以用优先队列存储度为0 */
        if(d[i]==0)
          q.push(i);
    while(!q.empty())
    {
        int t=q.top();
        q.pop();
        cout<<t<<' ';
        for(i=1;i<=n;i++)
            if(e[t][i])
        {
            d[i]--;
            if(d[i]==0)/**< 度为0入队 */
                q.push(i);
        }
    }
    return 0;
}

(二)关键路径

图结构中从起点到终点的最长路径,常用于计算工程项目的最早完成时间。下图起点1到终点6的关键路径(最长路径)为1456。

算法思路:如上图V5的路径必须经过V2或V4,因此V5最长路径dis[5]的值由dis[2]和dis[4]决定。

dis[5]=max(dis[2]+(2,5),dis[4]+(4,5)]

算法使用拓扑排序相同的处理过程,在拓扑排序过程中计算最长路径(最早发生时间)。

#include <iostream>
#include <queue>
using namespace std;/**< dis数组记录路径长度 */
int n,m,a[105][105],v[1005],d[1005],dis[1005];
int main()
{
    ios::sync_with_stdio(0),cin.tie(0);
    int i,j,x,y,z;
    cin>>n>>m;
    for(i=1; i<=m; i++)
    {
        cin>>x>>y>>z;
        a[x][y]=z;
        d[y]++;
    }
    queue<int>q;
    q.push(1);/**< 唯一入度为0的点 */
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        for(i=1; i<=n; i++)
        {
            if(a[t][i])
            { /**< 关键路径和拓扑排序唯一区别在于此处计算最长路径 */
                dis[i]=max(dis[i],dis[t]+a[t][i]);
                d[i]--;
                if(d[i]==0)
                    q.push(i);
            }
        }
    }
    cout<<dis[n];
    return 0;
}

那么如何得到关键路径呢?此处并没有使用教材上的求最迟发生时间的方法。可以借鉴求最短路径的方法,用一个p数组记录前驱结点。

#include <iostream>
#include <queue>
using namespace std;/**< dis数组记录路径长度 */
int n,m,a[105][105],v[1005],d[1005],dis[1005],path[105];
void print(int x)/**< 递归输出最长路径结点序列 */
{
    if(x==0)
        return ;
    print(path[x]);
    cout<<x<<' ';
}
int main()
{
    ios::sync_with_stdio(0),cin.tie(0);
    int i,j,x,y,z;
    cin>>n>>m;
    for(i=1; i<=m; i++)
    {
        cin>>x>>y>>z;
        a[x][y]=z;
        d[y]++;
    }
    queue<int>q;
    q.push(1);/**< 唯一入度为0的点 */
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        for(i=1; i<=n; i++)
        {
            if(a[t][i])
            {
                /**< 关键路径和拓扑排序唯一区别在于此处计算最长路径 */
                if(dis[i]<dis[t]+a[t][i])
                {
                    dis[i]=dis[t]+a[t][i];
                    path[i]=t;/**< 记录i的前驱结点为t */
                }
                d[i]--;
                if(d[i]==0)
                    q.push(i);
            }
        }
    }
    //print(n);
    cout<<dis[n];
    return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
拓扑排序是一种对有向无环进行排序的算法,它将中的节点按照它们的依赖关系进行排序,使得每个节点在排列中都在它的后继节点之前。拓扑排序可以用于解决很多问题,例如任务调度、编译顺序等。 关键路径算法是一种用于确定项目中关键任务的算法。它通过计算每个任务的最早开始时间和最晚开始时间,来确定哪些任务对项目的完成时间具有关键作用。关键路径算法可以帮助项目管理者确定项目的关键路径,从而更好地控制项目的进度。 以下是拓扑排序关键路径算法的详细步骤: 1. 拓扑排序: - 创建一个空列表result和一个空集合visited。 - 遍历中的每个节点,对于每个未访问过的节点,调用dfs函数进行深度优先遍历。 - 在dfs函数中,对于当前节点,将其加入visited集合中,并遍历其所有的后继节点。 - 对于每个后继节点,如果它未被访问过,则递归调用dfs函数。 - 在递归返回时,将当前节点加入result列表中。 - 最后将result列表反转,即可得到拓扑排序的结果。 2. 关键路径算法: - 对于每个任务,计算它的最早开始时间EST和最晚开始时间LST。 - 对于每个任务,计算它的最早完成时间EFT和最晚完成时间LFT。 - 对于每个任务,计算它的总浮动时间TF和自由浮动时间FF。 - 对于每个任务,如果它的TF为0,则它是关键任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值