【数据结构】——关键路径 给出一个图,输出所有关键路径

一、概述

给出一个图,首先判断是否为有向无环图,如果是,则输出要求的边的最早发生时间和最晚发生时间,然后输出所有关键路径。

判断是否为有向无环图,可以用拓扑排序来判断。

二、分析

首先分析拓扑排序。使用邻接矩阵储存图。

拓扑排序要用到的有:

入度数组,队列。

入度数组判断哪些元素入队。

如下:

int topo(int s)
{
	if(indegree[s]!=0)
	return 0;
	int num=0;
	queue<int> q;
	q.push(s);
	while(!q.empty())
	{
		int front=q.front();
		//vis[front]=1;
		num++;
		q.pop();
		for(int i=1;i<=N;i++)
		{
			if(G[front][i]!=0)
			{
				indegree[i]--;
				if(indegree[i]==0)
			{
				q.push(i);
			}
			}
			
		}
		/*for(int i=1;i<=N;i++)
		{
			if(indegree[i]==0)
			{
				q.push(i);
			}
		}*/
	}
	if(num==N)
	return 1;
	else
	return 0;
}

与层序遍历有些像,都是用队列,但是拓扑排序不需要vis数组,因为像这样写的拓扑排序,它根本就不会往回走,也就不需要判断是否走过了。一定注意一点,出队一个,然后遍历入度数组时,若减一出现了0,就要把它入队,这样才是正确的。不能先一个循环减一,再一个循环入队,那样就错了。

然后判断走过的节点个数,若是走完了,那就是无环,否则就是有环。

然后我们看关键路径需要的四大数组:

ve,vl,e,l:

ve:点的最早开始时间

vl:点的最迟开始时间

e:边的最早开始时间

l:边的最迟开始时间

在拓扑排序过程中,我们就可以确定ve的值了。

首先,源点的ve为0。源点相邻的下一个点的ve值,等于“所有指向这个点的ve的值加上他们之间边的边权中的最大值”。

我们观察一下拓扑排序的代码,在遍历某一点的所有边的时候,我们可以得到它指向的所有点,那么,就可以更新对应点的ve,如果和大于ve,则更新,否则不更新。

然后看vl。vl也有一个点可以确定,就是汇点,汇点的vl与汇点的ve是相等的。

剩余点的vl值,等于“它所指向的点的vl减去他们之间边权,这些值中的的最小值”。也就是说,我们需要从汇点,往回反拓扑排序。这时,可以使用栈,在拓扑排序时保存拓扑序列,然后出栈,就是我们要的反拓扑排序了。

这样所需要的拓扑排序函数如下:

int topo(int s)
{
	if(indegree[s]!=0)
	return 0;
	int num=0;
	queue<int> q;
	q.push(s);
    //这里保存反拓扑排序
	adtopo.push(s);
	ve[s]=0; 
	while(!q.empty())
	{
		int front=q.front();
		//vis[front]=1;
		num++;
		adtopo.push(front);
		q.pop();
		for(int i=1;i<=N;i++)
		{
			if(G[front][i]!=0)
			{
				indegree[i]--;
                //这里更新ve
				if(ve[i]<ve[front]+G[front][i])
				ve[i]=ve[front]+G[front][i];
				if(indegree[i]==0)
			{
				q.push(i);
			}
			}
			
		}
	}
	if(num==N)
	return 1;
	else
	return 0;
}

然后更新vl。

我们的反拓扑排序序列存在栈中,则栈顶就是汇点。于是汇点的vl确定了。

将汇点弹出,进入循环。对于目前的栈顶,可以知道它所有的指向的值(如果是汇点后面的一个,那只有一个,就是汇点),根据这些指向的值的vl,可以更新该点的vl。

简而言之,ve是一个更新一大堆,vl是一大堆更新一个。如下:

if(flag==1)
	{
		int d=adtopo.top();
		fill(vl,vl+1010,INF);
		vl[d]=ve[d];
		adtopo.pop();
		while(!adtopo.empty())
		{
			int top=adtopo.top();
			adtopo.pop();
			for(int i=1;i<=N;i++)
			{
				if(G[top][i]!=0)
				{
					if(vl[i]-G[top][i]<vl[top])
					vl[top]=vl[i]-G[top][i];
				}
			}
		}
	}

既然ve和vl更新完了,我们可以开始着手处理e和l了。

一条边的最早开始时间,就应该是它的起点的最早开始时间;

一条边的最晚开始时间,就应该是它的终点的最晚开始时间减去边权。

明确了这两点,e和l也就不难求了。

如下:

for(int i=1;i<=N;i++)
	{
		for(int j=1;j<=N;j++)
		{
			if(G[i][j]!=INF)
			{
				e[i][j]=ve[i];
				l[i][j]=vl[j]-G[i][j];
				if(e[i][j]==l[i][j])
				cri[i][j]=1;
			}
		}
	}

如果有多个源点或者多个汇点,那么设置一个大源点0,一个大汇点N+1,如下:

vector<int> r,d;
	for(int i=1;i<=N;i++)
	{
		if(indegree[i]==0)
		{
			//r=i;
			r.push_back(i);
			//break;
		}
	}
	for(int i=0;i<r.size();i++)
	{
		G[0][r[i]]=0;
		indegree[r[i]]++;
	}
int MAX=0;
		for(int i=1;i<=N;i++)
		{
			if(ve[i]>MAX)
			{
				MAX=ve[i];
			}
		}
		for(int i=1;i<=N;i++)
		{
			if(ve[i]==MAX)
			d.push_back(i);
		}
		for(int i=0;i<d.size();i++)
		{
			G[d[i]][N+1]=0;
		}
		fill(vl,vl+1010,INF);
		ve[N+1]=ve[d[0]];
		vl[N+1]=ve[N+1];

对应修改即可。

输出关键路径,就是对cri矩阵进行DFS即可。如下:

void DFS(int s,int d)
{
	if(s==d)
	{
		//ans.push_back(s);
		int i;
		for(i=0;i<ans.size()-1;i++)
		{
			printf("%d->",ans[i]);
		}
		printf("%d\n",ans[i]);
		//ans.pop_back();
		return;
	}
	else
	{
		if(s!=0)
		ans.push_back(s);
		vis[s]=1;
		for(int i=1;i<=N+1;i++)
		{
			if(cri[s][i]!=INF&&vis[i]==0)
			DFS(i,d);
		}
		vis[s]=0;
		if(s!=0)
		ans.pop_back();
	}
}

三、总结

核心就是拓扑排序函数,根据这个函数可以得到ve,而ve是万恶之源,通过一对多的思想更新ve,通过多对一的思想更新vl。

ve和vl出来了,这就出来一大半了。el简直就是白给的。然后输出关键路径即可。

PS:代码如下:

#include<stdio.h>
#include<cstdio>
#include<string>
#include<cstring>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<iostream>
#include<math.h>
#include<stack>
#include<algorithm>
#include<unordered_map>
using namespace std;
int INF=0x3f3f3f3f;
int G[1010][1010]={0};
int indegree[1010]={0};
int ve[1010]={0};//点的最早开始时间,找最大值 
int vl[1010];//点的最迟开始时间,找最小值 
int e[1010][1010]={0};//边的最早开始时间 
int l[1010][1010]={0};//边的最迟开始时间 
int cri[1010][1010];
//有向图是不用vis数组的,因为根本就回不去 
//int vis[1010]={0};
vector<int> ans;
int vis[1010]={0};
int N,M;
void DFS(int s,int d)
{
	if(s==d)
	{
		//ans.push_back(s);
		int i;
		for(i=0;i<ans.size()-1;i++)
		{
			printf("%d->",ans[i]);
		}
		printf("%d\n",ans[i]);
		//ans.pop_back();
		return;
	}
	else
	{
		if(s!=0)
		ans.push_back(s);
		vis[s]=1;
		for(int i=1;i<=N+1;i++)
		{
			if(cri[s][i]!=INF&&vis[i]==0)
			DFS(i,d);
		}
		vis[s]=0;
		if(s!=0)
		ans.pop_back();
	}
}
stack<int> adtopo;
int topo(int s)
{
	if(indegree[s]!=0)
	return 0;
	int num=0;
	queue<int> q;
	q.push(s);
	adtopo.push(s);
	ve[s]=0; 
	while(!q.empty())
	{
		int front=q.front();
		//vis[front]=1;
		num++;
		adtopo.push(front);
		q.pop();
		for(int i=1;i<=N;i++)
		{
			if(G[front][i]!=INF)
			{
				indegree[i]--;
				if(ve[i]<ve[front]+G[front][i])
				ve[i]=ve[front]+G[front][i];
				if(indegree[i]==0)
			{
				q.push(i);
			}
			}
			
		}
	}
	if(num==N+1)
	return 1;
	else
	return 0;
}
//大源点0大汇点N+1 
int main()
{
	scanf("%d %d",&N,&M);
	fill(G[0],G[0]+1010*1010,INF);
	fill(cri[0],cri[0]+1010*1010,INF);
	for(int i=1;i<=M;i++)
	{
		int a,b,w;
		scanf("%d %d %d",&a,&b,&w);
		G[a][b]=w;
		indegree[b]++;
	}
	vector<int> r,d;
	for(int i=1;i<=N;i++)
	{
		if(indegree[i]==0)
		{
			//r=i;
			r.push_back(i);
			//break;
		}
	}
	for(int i=0;i<r.size();i++)
	{
		G[0][r[i]]=0;
		indegree[r[i]]++;
	}
	
	int flag=topo(0);
	if(flag==0)
	{
		printf("NO\n");
		return 0;
	}
	else
	{
		printf("YES\n");
		//int d=adtopo.top();
		int MAX=0;
		for(int i=1;i<=N;i++)
		{
			if(ve[i]>MAX)
			{
				MAX=ve[i];
			}
		}
		for(int i=1;i<=N;i++)
		{
			if(ve[i]==MAX)
			d.push_back(i);
		}
		for(int i=0;i<d.size();i++)
		{
			G[d[i]][N+1]=0;
		}
		fill(vl,vl+1010,INF);
		ve[N+1]=ve[d[0]];
		vl[N+1]=ve[N+1];
		for(int i=0;i<d.size();i++)
		vl[d[i]]=vl[N+1];
		//adtopo.pop();
		while(!adtopo.empty())
		{
			int top=adtopo.top();
			adtopo.pop();
			for(int i=1;i<=N;i++)
			{
				if(G[top][i]!=INF)
				{
					if(vl[i]-G[top][i]<vl[top])
					vl[top]=vl[i]-G[top][i];
				}
			}
		}
	for(int i=0;i<=N+1;i++)
	{
		for(int j=0;j<=N+1;j++)
		{
			if(G[i][j]!=INF)
			{
				e[i][j]=ve[i];
				l[i][j]=vl[j]-G[i][j];
				if(e[i][j]==l[i][j])
				cri[i][j]=1;
			}
		}
	}
	int K;
	scanf("%d",&K);
	for(int i=0;i<K;i++)
	{
		int a,b;
		scanf("%d %d",&a,&b);
		printf("%d %d\n",e[a][b],l[a][b]);
	}
	printf("%d\n",ve[N+1]);
	DFS(0,N+1);
}
}

 

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值