虽然可以实现基本的求图的关键路径的功能,但是存在瑕疵,必须保证图的下标最小的节点是整个图的源点,下标最大的节点是整个图的汇点 (工程编码思维)
1.求图的拓扑排序
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#define FAST ios::sync_with_stdio(false)
#define LL long long
using namespace std;
const int N = 1e5 + 10;
int Head[N],tot,st[N],n,m,indegree[N];
stack<int> s; // 用来存当前入度为0的顶点的序号
struct E{
int to,next;
}Edge[N];
void add(int a,int b) // 读入边的操作
{
Edge[tot].to = b;
Edge[tot].next = Head[a];
Head[a] = tot ++;
}
int find_du(void) // 遍历确定图中每一个点的入度
{
for(int i = 1;i <= n;i++)
{
for(int j = Head[i] ; j != -1 ; j = Edge[j].next)
{
indegree[Edge[j].to] ++;
}
}
}
vector<int> Topsort()
{
for(int i = 1;i <= n;i++)
{
if(indegree[i] == 0)
{
s.push(i);
}
} // 先存入初始的时候度就为 0 的点
int count = 0; // 用来记录当前确定了拓扑序列的点的数目
vector<int> ans;
while(!s.empty()) // 每次取出一个入度为0的点,同时删去与这个点有关的所有边,创造新的入度为0的边
{
int temp = s.top();
count ++ ;
s.pop();
ans.push_back(temp);
for(int i = Head[temp] ; i != -1 ; i = Edge[i].next)
{
indegree[Edge[i].to] -- ; // 删除以这个点为起点的终点的顶点入度
if(indegree[Edge[i].to] == 0) s.push(Edge[i].to); // 检查是否有新的入度为0的点产生
}
}
if(count == n) // 这个图中的所有的顶点都可以确定其的拓扑序列位置
{
cout << "Yes" << endl;
return ans;
}
else
{
cout << "No" << endl;
ans.clear();
return ans;
}
}
int main(void)
{
FAST;
memset(Head,-1,sizeof Head);
cin >> n >> m;
while(m --)
{
int a,b;
cin >> a >> b;
add(a,b); // 有向无环图
}
find_du();
vector<int> Top = Topsort();
for(auto it : Top) cout << it << " ";
return 0;
}
- 上述+ 求每个节点的最早发生时间 + 求图的逆拓扑排序
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#define FAST ios::sync_with_stdio(false)
#define LL long long
using namespace std;
const int N = 1e5 + 10;
int Head[N],tot,st[N],n,m,indegree[N],ve[N]; // ve为每个事件的最早发生时间 (即到根节点的最远距离)
stack<int> s; // 用来存当前入度为0的顶点的序号
struct E{
int to,next,w;
}Edge[N];
void add(int a,int b,int w) // 读入边的操作
{
Edge[tot].to = b;
Edge[tot].next = Head[a];
Edge[tot].w = w;
Head[a] = tot ++;
}
int find_du(void) // 遍历确定图中每一个点的入度
{
for(int i = 1;i <= n;i++)
{
for(int j = Head[i] ; j != -1 ; j = Edge[j].next)
{
indegree[Edge[j].to] ++;
}
}
}
stack<int> Topsort() // 返回逆序的拓扑排序序列
{
for(int i = 1;i <= n;i++)
{
if(indegree[i] == 0)
{
s.push(i);
}
} // 先存入初始的时候度就为 0 的点
int count = 0; // 用来记录当前确定了拓扑序列的点的数目
stack<int> ans;
memset(ve,0,sizeof ve); // 初始化ve数组
while(!s.empty()) // 每次取出一个入度为0的点,同时删去与这个点有关的所有边,创造新的入度为0的边
{
int temp = s.top();
count ++ ;
s.pop();
ans.push(temp);
for(int i = Head[temp] ; i != -1 ; i = Edge[i].next)
{
indegree[Edge[i].to] -- ; // 删除以这个点为起点的终点的顶点入度
if(indegree[Edge[i].to] == 0) s.push(Edge[i].to); // 检查是否有新的入度为0的点产生
// 当前节点到根节点的距离 + 当前节点到其临边的边的权值 > 其临边所指向节点到根节的距离 就 直接更新这个临边节点到根节点的距离
if(ve[temp] + Edge[i].w > ve[Edge[i].to]) ve[Edge[i].to] = ve[temp] + Edge[i].w;
}
}
if(count == n) // 这个图中的所有的顶点都可以确定其的拓扑序列位置
{
cout << "Yes" << endl;
return ans;
}
else
{
cout << "No" << endl; // 不存在拓扑排序的话就直接结束程序的运行
exit(0);
}
}
int main(void)
{
FAST;
memset(Head,-1,sizeof Head);
cin >> n >> m;
while(m --)
{
int a,b,w;
cin >> a >> b >> w;
add(a,b,w); // 有向无环图
}
find_du();
stack<int> Verse_Top = Topsort();
return 0;
}
- 上述 + 求每个节点的最晚发生时间 + 求图的关键路径
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#define FAST ios::sync_with_stdio(false)
#define LL long long
using namespace std;
const int N = 1e5 + 10;
int Head[N],tot,st[N],n,m,indegree[N],ve[N],vl[N]; // ve为每个事件的最早发生时间 (即到根节点的最远距离) vl[N]为每个事件的最迟开始时间(逆拓扑序列依次减去边权值即可)
stack<int> s; // 用来存当前入度为0的顶点的序号
struct E{
int to,next,w;
}Edge[N];
void add(int a,int b,int w) // 读入边的操作
{
Edge[tot].to = b;
Edge[tot].next = Head[a];
Edge[tot].w = w;
Head[a] = tot ++;
}
int find_du(void) // 遍历确定图中每一个点的入度
{
for(int i = 1;i <= n;i++)
{
for(int j = Head[i] ; j != -1 ; j = Edge[j].next)
{
indegree[Edge[j].to] ++;
}
}
}
stack<int> Topsort() // 返回逆序的拓扑排序序列
{
for(int i = 1;i <= n;i++)
{
if(indegree[i] == 0)
{
s.push(i);
}
} // 先存入初始的时候度就为 0 的点
int count = 0; // 用来记录当前确定了拓扑序列的点的数目
stack<int> ans;
memset(ve,0,sizeof ve); // 初始化ve数组
while(!s.empty()) // 每次取出一个入度为0的点,同时删去与这个点有关的所有边,创造新的入度为0的边
{
int temp = s.top();
count ++ ;
s.pop();
ans.push(temp);
for(int i = Head[temp] ; i != -1 ; i = Edge[i].next)
{
indegree[Edge[i].to] -- ; // 删除以这个点为起点的终点的顶点入度
if(indegree[Edge[i].to] == 0) s.push(Edge[i].to); // 检查是否有新的入度为0的点产生
// 当前节点到根节点的距离 + 当前节点到其临边的边的权值 > 其临边所指向节点到根节的距离 就 直接更新这个临边节点到根节点的距离
if(ve[temp] + Edge[i].w > ve[Edge[i].to]) ve[Edge[i].to] = ve[temp] + Edge[i].w;
}
}
if(count == n) // 这个图中的所有的顶点都可以确定其的拓扑序列位置
{
cout << "Yes" << endl;
return ans;
}
else
{
cout << "No" << endl; // 不存在拓扑排序的话就直接结束程序的运行
exit(0);
}
}
void Find_CriticalPath(stack<int> T) // 寻找关键路径的函数
{
vector<int> path;
if(T.empty())
{
cout << "Stakc T is empty " << endl;
return ;
}
for(int i = 1;i <= n;i++) // 初始化所有节点的最迟发生时间为汇点的最早发生时间 再来进行依次的减去边权的操作
{
vl[i] = ve[n]; // 人为规定下标最大的节点是图中的汇点 (没错 工程编程思维,最后一个节点就是整个图的汇点)
}
while(!T.empty()) // 去处理拓扑排序 (间接地去处理每一个节点)
{
int temp = T.top();
T.pop();
for(int i = Head[temp] ; i != -1 ; i = Edge[i].next)
{
// 如果一个节点是多条边的起点 在反过来找这个节点的最晚发生时间的时候 是找边终点减去边权值的最小值作为该节点的最晚发生时间
if(vl[Edge[i].to] - Edge[i].w < vl[temp]) vl[temp] = vl[Edge[i].to] - Edge[i].w;
}
}
for(int i = 1;i <= n;i++) // 依次去遍历每一个节点 又从每一个点出发遍历这个点的所有临点
{
for(int j = Head[i] ; j != -1 ; j = Edge[j].next) // 遍历临点
{
int ei = ve[i]; // 当前起点的最早发生时间
int li = vl[Edge[j].to] - Edge[j].w; // 临点的最迟发生时间 - 临点到起点的边的权值 (倒推起点的最迟发生时间的前一步)
if(ei == li) // 当前节点的最早发生时间 和 最迟发生时间相等 这个节点以及使得其满足条件的这条边的终点 构成的边成为关键路径
// Tips : 每条关键路径的成立依托两个点
{
cout << i << " " << Edge[j].to << endl;
}
}
}
}
int main(void)
{
FAST;
memset(Head,-1,sizeof Head);
cin >> n >> m;
while(m --)
{
int a,b,w;
cin >> a >> b >> w;
add(a,b,w); // 有向无环图
}
find_du();
stack<int> Verse_Top = Topsort();
Find_CriticalPath(Verse_Top);
return 0;
}