关键路径 概念+思路+代码

注:关键路径的基础是拓扑排序,相关内容请看我的前一篇稿子。

概念

//AOV:在DAG(有向无环图)中,定点表示活动,边表示活动之间的次序。

//AOE:在DAG(有向无环图)中,定点表示事件,用带权有向边表示活动,边权表示活动持续时间。

//事件:瞬间发生的事情 eg:拿到录取通知书

//活动:一个完整的流程 eg:大一生活

//起点事件到终点事件有多条路径,当所有路径上所有事件都完成时,才标志着整个工程的完成,

//最长路径 就是 关键路径(关键路径不唯一)

思路

//找关键路径->找关键活动(关键路径由关键活动组成)

//关键活动特点:不能延期 (活动最早开始时间==活动最晚开始时间)

//活动的开始由事件的开始决定 so 找事件的各种时间

//

// eg: | i |--w-->| j |    下面公式基于该样例

//( i,j是事件,w是活动的权值,这是某关键路径的一部分)

//1.事件的最早发生时间(ETV)

//起点事件:ETV = 0; 

//其他事件:用topo序列算:ETV[j] = max(ETV[j] , ETV[i] + w);

//2.事件的最晚发生时间(LTV)

//起点事件:LTV = 0;  终点事件:LTV[e] = ETV[e];(规定工程不能延期)

// 其他事件:基于逆topo序列:LTV[i] = min( LTV[i] , LTV[j] - w);

//3.活动的最早发生时间(ETE)

// ETE = ETV[i];

//4.活动的最晚发生时间(LTE)

//LTE = 该边终点事件的LTV[j] - 该边权值w

//5.关键活动 LTE == ETE -->关键路径

时间复杂度:(n + m);//二次遍历整个邻接表

代码

(文章结尾提供有测试数据)

//邻接表 存 有向带权图
#include<iostream>
#include<cstdlib>
#define INF 9999
using namespace std;
//--------------------------------------------------------
struct stack// 链栈
{
	int data;//记录下标
	stack* next;
};
stack* init()//栈的初始化
{
	stack* s = (stack*)malloc(sizeof(stack));
	if (s == nullptr) exit(0);
	s->next = NULL;
	return s;
}
void insert(stack* s, int k)//栈 插入
{
	stack* p = (stack*)malloc(sizeof(stack));
	if (p == NULL)return;
	p->data = k;
	p->next = s->next;
	s->next = p;
}
int del(stack* s)//栈 删除
{
	if (s->next == NULL)
	{
		cout << "del no" << endl;
		return -1;
	}
	stack* p = s->next;
	s->next = p->next;
	int x = p->data;
	free(p); p = NULL;
	return x;
}
//--------------------------------------------------------
struct enode//节点后接邻接点链
{
	int data;//记录下标
	enode* next;
	int w;//比拓扑排序多一个w
};
struct vnode//结点
{
	char data;
	enode* first;
};
vnode vertex[105];//顶点数组
int n, m;//点数 边数
int ind[105];//记录每个点的入度
int topo[105];//保存拓扑序列--基于定点下标,便于使用
int etv[105];//事件最早发生时间
int ltv[105];//事件最晚发生时间
//找顶点数组中data为x的元素下标
int find(char x)
{
	for (int i = 0; i < n; i++)
	{
		if (vertex[i].data == x)
		{
			return i;
		}
	}
	return -1;//找不到返回-1
}

void toposort()
{
	stack* s = init();//初始化栈
	for (int i = 0; i < n; i++)
	{
		//初始化etv数组,一开始认为所有事件最早发生时间都是0
		etv[i] = 0;
		//先将入度为零的点作为起始点入栈
		if (ind[i] == 0)//这里认为入度为0的点只有一个
			insert(s, i);
	}
	int t;//出栈元素下标
	int k = 0;//topo数组的下标
	for (int i = 0; i < n; i++)
	{
		t = del(s);
		topo[k] = t;//把出栈顶点下标保存在topo序列中
		k++;//加入拓扑序列中
		enode* p = vertex[t].first;
		while (p != NULL)
		{
			int j = p->data;//j是t的出边邻接点  t ----> j
			int wi = p->w;
			if (etv[j] < (etv[t] + wi))
			{//求etv
				etv[j] = etv[t] + wi;
			}
			ind[j]--;
			if (ind[j] == 0)//将以出栈顶点为出发点相连的顶点入度-1
			{//-1后出现入度为0的点则入栈
				insert(s, j);
			}
			p = p->next;
		}
	}
}

void Criticalpath()
{
	int end = topo[n - 1];
	for (int i = 0; i < n; i++)
	{
		ltv[i] = etv[end];//初始化ltv
		//将每个点的最晚发生时间都初始化为终点的最早发生时间即可
	}
	int x;
	for (int i = n - 2; i >= 0; i--)
	{
		x = topo[i];//求x的最晚发生时间
		enode* p = vertex[x].first;
		while (p != NULL)
		{//求ltv
			int j = p->data;
			if (ltv[x] > (ltv[j] - (p->w)))
			{
				ltv[x] = (ltv[j] - (p->w));
			}
			p = p->next;
		}
	}
	cout << "以下是关键活动:" << endl;
	int ete, lte;
	for (int i = 0; i < n; i++)
	{
		for (enode* p = vertex[i].first; p != NULL; p = p->next)
		{
			int j = p->data;
			ete = etv[i];
			lte = ltv[j] - (p->w);
			if (ete == lte)
				cout << vertex[i].data << " " << vertex[j].data << endl;
		}
	}
}

int main()
{
	cin >> n >> m;
	for (int i = 0; i < n; i++)//存顶点数组
	{
		cin >> vertex[i].data;//A B C D...
		vertex[i].first = NULL;
	}
	char x, y; int xi, yi, wi;
	for (int i = 0; i < m; i++)
	{
		cin >> x >> y >> wi;//输入边  始  终  权值
		xi = find(x); if (xi == -1)return 0;
		yi = find(y); if (yi == -1)return 0;//防止栈溢出警报
		ind[yi]++;//边的终点入度+1
		//邻接表数据的插入
		enode* p = (enode*)malloc(sizeof(enode));
		if (p == NULL)return 0;
		p->data = yi;
		p->w = wi;
		p->next = vertex[xi].first;//头插
		vertex[xi].first = p;
	}
	toposort();
	Criticalpath();
	return 0;
}

/*

测试数据

9 11

A B C D E F G H I

A B 6

A C 4

A D 5

B E 1

C E 1

D F 2

E G 9

E H 7

F H 4

G I 2

H I 4

结果:

以下是关键活动:

A B

B E

E H

E G

G I

H I

*/

数据的图像:(1 - 9 对应 A - H)

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值