数据结构图之关键路径与拓扑排序的实现
算法实现#
#include<iostream>
#include<stack>
using namespace std;
typedef int Vertextype;
typedef struct ArcNode
{
int index;
int weight;
struct ArcNode* next;
}ArcNode;
typedef struct VNode
{
Vertextype data;
int indegree = 0;
ArcNode* firstAdj;
}VNode;
//生成图
void CreateGraph(VNode G[], int n); //n 顶点数
void TopSort(VNode G[], int n);
//void Cr(VNode G[], int n, int e);
//int main()
//{
//
// VNode G[100];
// int n;
// cin >> n;
// CreateGraph(G, n);
// TopSort(G, n);
// /*
// 9
// 1 6 2 4 3 5 -1
// 4 1 -1
// 4 1 -1
// 5 2 -1
// 6 9 7 7 -1
// 7 4 -1
// 8 2 -1
// 8 4 -1
// -1
// <0,1> lenth: 6
// <1,4> lenth: 1
// <4,6> lenth: 9
// <4,7> lenth: 7
// <6,8> lenth: 2
// <7,8> lenth: 4
// 一组供测试的数据
// */
// return 0;
//}
//
//
//
//生成图
/*
录入顶点和边数据
*/
void CreateGraph(VNode G[], int n)//n 顶点数
{
for (int i = 0; i < n; i++)
{
int v;
G[i].firstAdj = NULL;
G[i].data = i;
ArcNode* p = NULL, * q = NULL;
cin >> v;
while (v != -1)
{
p = new ArcNode;
p->index = v;
p->next = NULL;
cin >> p->weight;
G[v].indegree += 1;
if (G[i].firstAdj == NULL)
{
G[i].firstAdj = p;
}
else
{
q->next = p;
}
cin >> v;
q = p;
}
}
}
// 关键路径适用于AOE网,即用边表示活动,顶点表示事件
//
//拓扑排序求关键路径
void TopSort(VNode G[], int n)
{
int* ETV = new int[n]; //事件最早发生时间
/*
* 一个事件可以开始进入到活动,需要它的前置条件都完成,因此事件的最早发生时间是从
* 源点到此点最长的权值路径,因此既要满足拓扑排序又要求路径最大值
*/
int* LTV = new int[n]; //事件最晚发生时间
/*
* 不影响整个工程该事件的最晚发生时间,即 用它下一个事件 的 最晚发生时间 减去 它完成两个事件间的活动
* 权值所得到的结果
*/
int* Ans = new int[n]; //记录拓扑排序节点序列
int count = 0; //记录已拓扑排序的节点数,比n小说明该图中存在环
stack<int> s; //初始化一个栈来进行拓扑排序
for (int i = 0; i < n; i++)
{
ETV[i] = 0;
LTV[i] = 0;
if (G[i].indegree == 0) //遍历图的节点,找到入度为0的那个源点加入到栈中
s.push(i);
}
int p = 0;
while (!s.empty())
{
p = s.top(); //出栈获得节点
s.pop();
Ans[count++] = p; //栈内的节点肯定是入度为0的节点,因此直接加入到拓扑序列中存储
ArcNode* q = G[p].firstAdj; //q用于遍历p节点的所有邻接点
while (q != NULL)
{
/*
拓扑排序是通过从图中去掉该入度为0的节点,并将所有以该节点为起始节点的边去掉,
因此直接将该节点连接的另外节点的入度减一就可以到达等效的目的
*/
G[q->index].indegree -= 1; //邻接点的入度减一
if (ETV[p] + q->weight > ETV[q->index]) //如果p的最早发生时间加上完成该边上的活动权值大于
{ //邻接点q的最早发生时间,就进行时间更新,保证最早发生时间是最长的路径
ETV[q->index] = ETV[p] + q->weight;
}
if (G[q->index].indegree == 0)//如果邻接点入度减1后入的变为了0,就将该邻接点入栈用于下一论拓扑排序
{
s.push(q->index);
}
q = q->next; //将q赋为下一个邻接点
}
}
/*
AOE网的性质,最后一个拓扑序列的点必定是汇点,所对应的最早发生时间就是完成该工程的时间
*/
for (int i = 0; i < n; i++)
LTV[i] = ETV[n - 1]; //将最晚发生时间都初始化为完成工程的时间
for (int i = n - 2; i >= 0; i--) //这里从汇点前面一个节点往初始节点开始计算,因为汇点的最早发生时间和最晚发生时间都是完成工程的时间
{
ArcNode* p = G[i].firstAdj; //p是节点G[i]的邻接点
while (p != NULL)
{
if (LTV[G[i].data] > LTV[p->index] - p->weight) //邻接点的最晚发生时间减去两事件间活动的权值即为最晚发生事件
{/*
另外 这里为什么是进行小于比较,我想的是:规定一个事件最晚要在6号开始动工,那么你就不能拖到7号开始,那样就延误了时间
所以这里是进行小于比较
*/
LTV[G[i].data] = LTV[p->index] - p->weight; //如果小于就进行最晚发生事件的更新
}
p = p->next; //进入到下一个邻接点
}
}
int ete, lte; //ete是活动最早发生时间,lte是活动最晚发生时间
/*
活动的最早发生时间是与它前面的时间的最早发生时间相同,事件的发生就代表事件后面的活动可以开始了。
活动最晚发生时间就是它后面事件的最晚发生时间减去活动的权值,这就是活动最晚发生时间了。如:现在有事件u,v
u,v间的活动是<u,v>,活动权值假设为5,整个工程时间是18,而v的最晚发生时间是10,因此要想v不延迟,那么活动<u,v>
最晚要在10的时候结束,因此要在10-5时候开始,即<u,v>的最晚发生时间是10-5
*/
for (int i = 0; i < n; i++)
{
ArcNode* p = G[i].firstAdj; //p是事件i的邻接点
while (p != NULL)
{
ete = ETV[i]; //ete是活动最早发生时间,因此与事件i的最早发生时间相同
lte = LTV[p->index] - p->weight;//lte是活动最晚发生时间,因此找到i的邻接点的最晚发生时间并减去他们直接活动所需的权值可求得
if (ete == lte) //关键路径的特点是最早发生时间和最晚发生时间相同的活动,如果该活动的ete与lte相等即表明
{ //该活动是关键路径
cout << "<" << i << "," << p->index << "> lenth: " << p->weight << endl;
}
p = p->next; //p赋为下一个邻接点
}
}
}
作者:墨鱼-yyyl
出处:https://www.cnblogs.com/moyu-yyyl/p/18009719
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通