图的关键路径算法

1. 相关概念

1.1 AOE网

用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以弧表示活动,以顶点表示活动的开始或结束事件,称这种有向图为边表示活动的网,简称为AOE网(Activity On Edge network)。

2.关键路径

2.1 概念

把工程计划表示为边表示活动的网络,即AOE网,用顶点表示事件,弧表示活动,弧的权表示活动持续时间
事件表示在它之前的活动已经完成,在它之后的活动可以开始。
关键路径——路径长度最长的路径。
路径长度——路径上各活动持续时间之和。

2.2 关键路径算法

如何确定关键路径,需要定义4个描述量
etv(earliest time of vertex): 事件 v j v_{j} vj的最早发生时间;
ltv(latest time of vertex): 事件 v j v_{j} vj的最迟发生时间;
ete(earliest time of edge): 活动 a i a_{i} ai的最早开始时间;
lte(latest time of edge): 活动 a i a_{i} ai的最迟开始时间。

l t e ( a i ) − e t e ( a i ) lte(a_{i})-ete(a_{i}) lte(ai)ete(ai)表示完成活动 a i a_{i} ai的时间余量,若 l t e ( a i ) = e t e ( a i ) lte(a_{i})=ete(a_{i}) lte(ai)=ete(ai),则 a i a_{i} ai是关键活动。

如何找 l t e ( a i ) = e t e ( a i ) lte(ai)=ete(ai) lte(ai)=ete(ai)的关键活动?
设活动 a i a_{i} ai用弧<j, k>表示,其持续时间记为: w i , j w_{i,j} wi,j,则有:
(1) e t e ( i ) = e t v ( j ) ete(i) = etv(j) ete(i)=etv(j)
(2) l t v ( i ) = l t v ( k ) − w i , j ltv(i) = ltv(k) - w_{i,j} ltv(i)=ltv(k)wi,j
如何求etv(j)和ltv(j)?
(1)从 e t v ( 0 ) = 0 etv(0) = 0 etv(0)=0开始向前递推
e t v ( j ) = m a x { e t v ( i ) + w i , j } etv(j) = max\{ etv(i) + w_{i,j}\} etv(j)=max{etv(i)+wi,j} < i , j > ∈ T <i,j> \in T <i,j>T 1 ≤ j < n 1 \leq j<n 1jn,其中T是所有以j为头的弧的集合。
(2)从 l t v ( n − 1 ) = e t v ( n − 1 ) ltv(n-1) = etv(n-1) ltv(n1)=etv(n1)开始向后推
l t v ( i ) = m i n { l t v ( j ) − w i , j } ltv(i) = min\{ ltv(j) - w_{i,j}\} ltv(i)=min{ltv(j)wi,j} < i , j > ∈ S <i,j> \in S <i,j>S 0 ≤ j < n − 1 0 \leq j<n-1 0jn1,其中S是所有以i为尾的弧的集合。

算法复杂度: O ( n + e ) O(n+e) O(n+e)

注:一个图中关键路径可能不止一条

3. 代码

#include <iostream>
using namespace std;

/**************************************************************************************
******************************** 图的邻接表结构定义及操作 ********************************
***************************************************************************************/
typedef char VertexType;	// 顶点类型,自定义成char
typedef int  WeightType;	// 边上的权值类型,自定义成int

#define MAXVEX	 (100)		// 最大顶点数

/* 边表结点 */
typedef struct EdgeNode {
	int			adjvex;		// 邻接点域,存储该顶点对应的下标
	WeightType	weight;		// 权值域,存储顶点权值,非网图(无权图)不必定义
	EdgeNode* next;			// 链域,指向下一个邻接点
} EdgeNode;

/* 顶点结点 */
typedef struct VertexNode {
	int in;						// 顶点入度
	VertexType	data;			// 顶点域,存储顶点值
	EdgeNode* firstEdge;		// 边表头指针,指向第一个邻接点
} VertexNode, AdjList[MAXVEX];	// AdjList表示邻接表类型

typedef struct {
	AdjList adjList;		// 邻接表,相当于:VertexNode adjList[MAXVEX];
	int Nv, Ne;				// 图中顶点数和边数
} ALGraph;

/* 建立图的邻接表结构 */
void createALGraph(ALGraph* G) {
	cout << "输入顶点数和边数:";
	cin >> G->Nv >> G->Ne;

	// 读入顶点信息
	for (int i = 0; i < G->Nv; i++)
	{
		cout << "输入顶点数据及其入度:";
		cin >> G->adjList[i].data;			// 读入顶点数值
		cin >> G->adjList[i].in;			// 读入顶点入度
		G->adjList[i].firstEdge = nullptr;	// 将边表指针置为空
	}

	// 读入Ne条边,建立邻接表
	int i, j;
	WeightType w;
	EdgeNode* e;
	for (int k = 0; k < G->Ne; k++)
	{
		cout << "输入边(vi, vj)的下标i、j及权值w:";
		cin >> i >> j >> w;
		
		/*
		cout << "输入边(vi, vj)的下标i、j:";
		cin >> i >> j;
		*/

		e = new EdgeNode;
		e->adjvex = j;						// 邻接点下标为j
		e->weight = w;						// 边(vi, vj)上的权值
		e->next = G->adjList[i].firstEdge;	// 将e的next指针指向当前顶点firstEdge指向的结点
		G->adjList[i].firstEdge = e;		// 将当前顶点的firstEdge指针指向e
		/* 上面两行:使用头插法将结点插入链表中 */

		// 以下为无向图需要部分,有向图不需要 
		/*e = new EdgeNode;
		e->adjvex = i;
		e->weight = w;
		e->next = G->adjList[j].firstEdge;
		G->adjList[j].firstEdge = e;*/
	}
}

/* 销毁邻接表 */
void destroyAdjList(ALGraph* G) {
	for (int i = 0; i < G->Nv; i++)
	{
		while (G->adjList[i].firstEdge)
		{
			EdgeNode* e = G->adjList[i].firstEdge;
			G->adjList[i].firstEdge = e->next;
			delete e;
		}
	}
}

/* 全局变量 */
WeightType* etv, * ltv;	// 事件最早发生时间和最迟发生时间数组
int* stack2;			// 用于存储拓扑序列的栈
int top2;				// 用于stack2的栈顶指针

/**************************************************************************************
*************************************** 拓扑排序 ***************************************
***************************************************************************************/
/**
* @brief:	拓扑排序,若G无回路,则输出拓扑排序序列并返回0,若有回路返回-1
* @param G:	邻接表表示的图指针
* @return:	0 —— 成功
*			-1 —— 失败
*/
int TopologicalSort(ALGraph* G)
{
	EdgeNode* e;
	int i, k, gettop;
	int top = -1;	// 用于栈指针下标
	int count = 0;	// 用于统计输出顶点的个数
	int* stack;		// 建栈存储入度为0的顶点
	stack = new int[G->Nv];

	for (i = 0; i < G->Nv; i++)
	{
		if (G->adjList[i].in == 0)
		{
			stack[++top] = i;	// 将入度为0的顶点入栈
		}
	}

	/* 全局变量初始化 */
	top2 = -1;
	stack2 = new int[G->Nv];
	etv = new WeightType[G->Nv];	// 事件最早发生时间全部初始化尾0
	for (size_t i = 0; i < G->Nv; i++)
	{
		etv[i] = 0;
	}

	while (top != -1)
	{
		gettop = stack[top--];		// 出栈
		stack2[++top2] = gettop;	// 将弹出的顶点序号压入拓扑序列的栈
		count++;

		/* 遍历该顶点对应的边表 */
		for (e = G->adjList[gettop].firstEdge; e; e = e->next)
		{
			k = e->adjvex;
			G->adjList[k].in--;			// 将k号顶点的入度减1
			if (G->adjList[k].in == 0)	// 若入度为0,入栈
			{
				stack[++top] = k;
			}

			if (etv[gettop] + e->weight > etv[k])	// 求各顶点事件最早发生时间
			{
				etv[k] = etv[gettop] + e->weight;
			}
		}

	}

	delete[] stack;			// 释放分配的内存

	if (count < G->Nv)		// 如果count小于顶点数,说明存在环
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

/**************************************************************************************
*************************************** 关键路径 ***************************************
***************************************************************************************/
/**
* @brief:		关键路径
* @param G:		邻接表表示的图指针
* @return:		void
*/
void CriticalPath(ALGraph* G)
{
	TopologicalSort(G);				// 拓扑排序,计算数组etv和stack2
	ltv = new WeightType[G->Nv];	// 事件最晚发生时间,初始化为拓扑序列最后一个顶点的最早发生时间
	for (size_t i = 0; i < G->Nv; i++)
	{
		ltv[i] = etv[G->Nv - 1];
	}
	
	/* 求ltv */
	int gettop2, k;
	EdgeNode* e;
	while (top2 != -1)	// stack2不空
	{
		gettop2 = stack2[top2--];	// 拓扑序列出栈,后进先出,相当于将拓扑序列从尾至头遍历
		for (e = G->adjList[gettop2].firstEdge; e; e = e->next)
		{
			k = e->adjvex;
			if (ltv[k] - e->weight < ltv[gettop2])	// 求各顶点事件最晚发生事件
			{
				ltv[gettop2] = ltv[k] - e->weight;
			}
		}
	}
	
	cout << "stack2: ";
	for (size_t i = 0; i < G->Nv; i++)
	{
		cout << stack2[i] << "\t";
	}
	cout << "\netv: ";
	for (size_t i = 0; i < G->Nv; i++)
	{
		cout << etv[i] << "\t";
	}
	cout << "\nltv: ";
	for (size_t i = 0; i < G->Nv; i++)
	{
		cout << ltv[i] << "\t";
	}

	cout << "\ncritical path: ";
	WeightType ete, lte;					// 声明活动最早发生时间和最迟发生时间
	for (size_t j = 0; j < G->Nv; j++)		// 求 ete、lte,关键路径
	{
		for (e = G->adjList[j].firstEdge; e; e = e->next)
		{
			k = e->adjvex;
			ete = etv[j];				// 活动最早发生时间
			lte = ltv[k] - e->weight;	// 活动最迟发生时间
			if (ete == lte)				// 两者相等即在关键路径上		
			{
				cout << "<V" << j << ",V" << k << "> " << e->weight << "\t";
			}
		}
	}
}

int main()
{
	ALGraph* G = new ALGraph;
	// 建图
	createALGraph(G);

	// 关键路径
	CriticalPath(G);

	// 销毁邻接表
	destroyAdjList(G);

	delete G;
	delete[] etv;
	delete[] ltv;
	delete[] stack2;
}

运行结果:
以《大话数据结构》图7-9-4为例。
在这里插入图片描述

4. 参考书籍

大话数据结构-程杰
青岛大学数据结构-王卓

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MinBadGuy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值