注:关键路径的基础是拓扑排序,相关内容请看我的前一篇稿子。
概念
//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)