关键路径算法

校招常考算法 专栏收录该内容
83 篇文章 1 订阅

1,(部分转载)

关键路径的算法是建立在拓扑排序的基础之上的,这个算法中用到了拓扑排序,所以在这里先以拓扑排序开篇。

1. 什么是拓扑排序?
举个例子先:一个软件专业的学生学习一系列的课程,其中一些课程必须再学完它的基础的先修课程才能开始。如:在《程序设计基础》和《离散数学》学完之前就不能开始学习《数据结构》。这些先决条件定义了课程之间的领先(优先)关系。这个关系可以用有向图更清楚地表示。图中顶点表示课程,有向边表示先决条件。若课程i是课程j的先决条件,则图中有弧<i,j>。若要对这个图中的顶点所表示的课程进行拓扑排序的话,那么排序后得到的序列,必须是按照先后关系进行排序,具有领先关系的课程必然排在以它为基础的课程之前,若上例中的《程序设计基础》和《离散数学》必须排在《数据结构》之前。进行了拓扑排序之后的序列,称之为拓扑序列。
2. 如何实现拓扑排序?
很简单,两个步骤:
1. 在有向图中选一个没有前驱的顶点且输出。
2. 从图中删除该顶点和以它为尾的弧。
重复上述两步,直至全部顶点均已输出,或者当前图中不存在无前驱的顶点为止。后一种情况则说明有向图中存在环。
3. 什么是关键路径?
例子开头仍然,图1是一个假想的有11项活动的A0E-网。其中有9个事件v1,v2......,v9,每个事件表示在它之前的活动一完成,在它之后的活动可以开始。如v1表示整个工程的开始,v9表示整个工程结束,v5表示a4和a5已完成,a7和a8可以开始。与每个活动相联系的数是执行该活动所需的时间。比如,活动a1需要6天,a2需要4天。


由于整个工程只有一个开始点和一个完成点,故在正常情况(无环)下,网中只有一个入度为零的点(称作源点)和一个出度为零的点(叫做汇点)。
那么该工程待研究的问题是:1.完成整项工程至少需要多少时间?2.哪些活动是影响工程进度的关键?
由于在AOE-网中有些活动可以并行进行,所以完成工程的最短时间是从开始点到完成点的最长路径的长度(这里所说的路径长度是指路径上各活动持续时间之和,不是路径上弧的数目)。路径长度最长的路径叫做关键路径(Critical path)。
假设开始点是v1,从v1到vi的最长路径叫做时间vi的最早发生时间。这个时间决定了所有以vi为尾的弧所表示的活动的最早开始时间。我们用e(i)表示活动ai的最早开始时间。还可以定义一个活动开始的最迟时间l(i),这是在不推迟整个工程完成的前提下,活动ai最迟必须开始进行的时间。两者之差l(i)-e(i)意味着完成活动ai的时间余量。当这个时间余量等于0的时候,也即是l(i)=e(i)的活动,我们称其为关键活动。显然,关键路径上的所有活动都是关键活动,因此提前完成非关键活动并不能加快工程的进度。
因此,分析关键路径的目的是辨别哪些是关键活动,以便争取提高关键活动的功效,缩短整个工期。

4. 如何实现关键路径?
由上面的分析可知,辨别关键活动就是要找e(i)=l(i)的活动。为了求得e(i)和l(i),首先应求得事件的最早发生时间ve(j)和最迟发生时间vl(j)。如果活动ai由弧<j,k>表示,其持续时间记为dut(<j,k>),则有如下关系
e(i) = ve(j)
l(i) = vl(k) - dut(<j,k>)
求解ve(j)和vl(j)需分两个步进行:
1) 从ve(0)=0开始向前推进求得ve(j)
Ve(j) = Max{ve(i) + dut(<i,j>) };<i,j>属于T,j=1,2...,n-1
其中T是所有以第j个顶点为头的弧的集合。

2) 从vl(n-1) = ve(n-1)起向后推进求得vl(j)
vl(i) = Min{vl(j) - dut(<i,j>};<i,j>属于S,i=n-2,...,0
其中,S是所有以第i个顶点为尾的弧的集合。
这两个递推公式的计算必须分别在拓扑有序和逆拓扑有序的前提先进行。也就是说,ve(j-1)必须在vj的所有前驱的最早发生时间求得之后才能确定,而vl(j-1)必须在Vj的所有后继的最迟发生时间求得之后才能确定。因此可以在拓扑排序的基础上计算ve(j-1)和vl(j-1)。


具体算法描述如下:
1. 输入e条弧<j,k>,建立AOE-网的存储结构
2. 拓扑排序,并求得ve[]。从源点V0出发,令ve[0]=0,按拓扑有序求其余各顶点的最早发生时间ve[i]。如果得到的拓扑有序序列中顶点个数小于网中顶点数n,则说明网中存在环,不能求关键路径,算法终止;否则执行步骤3。
3. 拓扑逆序,求得vl[]。从汇点Vn出发,令vl[n-1] = ve[n-1],按逆拓扑有序求其余各顶点的最迟发生时间vl[i]。
4. 求得关键路径。根据各顶点的ve和vl值,求每条弧s的最早开始时间e(s)和最迟开始时间l(s)。若某条弧满足条件e(s) = l(s),则为关键活动。

为了能按逆序拓扑有序序列的顺序计算各个顶点的vl值,需记下在拓扑排序的过程中求得的拓扑有序序列,这就需要在拓扑排序算法中,增设一个栈,以记录拓扑有序序列,则在计算求得各顶点的ve值之后,从栈顶到栈底便为逆拓扑有序序列。


代码(自己编写:)

arcnode.h

#pragma once

class arcnode
{
public:
	arcnode(void);
	arcnode(int x, int y):vertex(x), val(y), nextarc(NULL){}
	~arcnode(void);
public:
	int vertex;
	int val;
	arcnode* nextarc;
};
node.h
#pragma once
#include "arcnode.h"
class node
{
public:
	node(void);
	node(int x):vertex(x), firstarc(NULL){}
	~node(void);
public:
	int vertex;
	arcnode* firstarc;
};
#pragma once
#include "node.h"
class Dgraph
{
public:
	Dgraph(void);
	~Dgraph(void);
	void createGraph(Dgraph &g);
public:
	int vernum;
	int arcnum;
	node* AdjList;
	int *indegree;
	int *outdegree;
};
Dgraph.cpp

#include "StdAfx.h"
#include "Dgraph.h"
#include <string>
#include <iostream>
#include <fstream>
using namespace std;


Dgraph::Dgraph(void)
{
}

Dgraph::~Dgraph(void)
{
}
void Dgraph::createGraph(Dgraph &g)
{
	ifstream infile("F:\\test100.txt");
	cout << "读取图的顶点数和边数!" << endl;
	infile >> g.vernum >> g.arcnum;
	cout << g.vernum << ' ' << g.arcnum << endl;
	cout << "开始建立邻接表!" << endl;
	g.indegree = new int[g.vernum];
	g.outdegree = new int[g.vernum];
	g.AdjList = new node[g.vernum];//这里只是调用了node默认的构造函数,所以firstarc还是要重新复制
	for (int i = 0; i < g.vernum; i++)
	{
		g.indegree[i] = 0;
		g.outdegree[i] = 0;
		g.AdjList[i].vertex = i;
		g.AdjList[i].firstarc = NULL;
	}
	int tail, head, val;
	for (int i = 0; i < g.arcnum; i++)
	{
		infile >> tail >> head >> val;
		cout << tail << ' ' << head << ' ' << val << endl;
		arcnode* temp = new arcnode(head, val);
		temp->nextarc = g.AdjList[tail].firstarc;
		g.AdjList[tail].firstarc = temp;
		g.indegree[head]++;
		g.outdegree[tail]++;
	}
}
关键路径.cpp

// 关键路径.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "Dgraph.h"
#include <vector>
#include <stack>
#include <iostream>
using namespace std;
struct A 
{
	int tail;
	int head;
};
void topuforearlyfinish(vector<int> &ve, Dgraph g, stack<int> &T)
{
	stack<int> s;
	for (int i = 0; i < g.vernum; i++)
	{
		if (g.indegree[i] == 0)
		{
			s.push(i);
			T.push(i);//入度为0的都入栈
		}
	}
	while (!s.empty())
	{
		int key = s.top();
		s.pop();
		for (arcnode* i = g.AdjList[key].firstarc; i; i = i->nextarc)
		{
			g.indegree[i->vertex]--;
			//下边是求顶点的最早完成时间
			if (ve[key] + i->val > ve[i->vertex])
			{
				ve[i->vertex] = ve[key] + i->val;
			}
			if (g.indegree[i->vertex] == 0)
			{
				s.push(i->vertex);
				T.push(i->vertex);
			}
		}
	}
}
void topuforlastfinish(Dgraph g, stack<int> T, vector<int> ve, vector<int> &vl)
{
	for (unsigned int i = 0; i < vl.size(); i++)
	{
		vl[i] = ve[g.vernum - 1];//最晚完成时间都初始化为最后一个顶点的最早发生时间
	}
	while (!T.empty())
	{
		int key = T.top();
		T.pop();
		for (arcnode* i = g.AdjList[key].firstarc; i; i = i->nextarc)
		{
			//下边就是求顶点的最晚完成时间
			if (vl[i->vertex] - i->val < vl[key])
			{
				vl[key] = vl[i->vertex] - i->val;
			}
		}
	}
}
void CriticalPath(Dgraph g, vector<int> ve, vector<int> vl, vector<A> &path)
{
	for (int i = 0; i < g.vernum; i++)
	{
		for (arcnode* j = g.AdjList[i].firstarc; j; j = j->nextarc)
		{
			if (vl[j->vertex] - j->val == ve[i])
			{
				A temp;
				temp.tail = i;
				temp.head = j->vertex;
				path.push_back(temp);
			}
		}
	}
}
int _tmain(int argc, _TCHAR* argv[])
{
	Dgraph g;
	g.createGraph(g);
	vector<int> ve(g.vernum, 0);
	vector<int> vl(g.vernum);
	stack<int> t;
	topuforearlyfinish(ve, g, t);
	topuforlastfinish(g, t, ve, vl);
	vector<A> path;
	CriticalPath(g, ve, vl, path);
	cout << "各项关键活动为:" << endl;
	for (unsigned int i = 0; i < path.size(); i++)
	{
		cout << path[i].tail << "->" << path[i].head << endl;
	}
	system("pause");
	return 0;
}
test100.txt

9 11
0 1 6
0 2 4
0 3 5
1 4 1
2 4 1
3 5 2
4 6 9
4 7 7
5 7 4
6 8 2
7 8 4

结果:

0->1

1->4

4->6

4->7

7->8

6->8


  • 2
    点赞
  • 0
    评论
  • 4
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值