【數據結構】——關鍵路徑 給出一個圖,輸出所有關鍵路徑

一、概述

給出一個圖,首先判斷是否爲有向無環圖,如果是,則輸出要求的邊的最早發生時間和最晚發生時間,然後輸出所有關鍵路徑。

判斷是否爲有向無環圖,可以用拓撲排序來判斷。

二、分析

首先分析拓撲排序。使用鄰接矩陣儲存圖。

拓撲排序要用到的有:

入度數組,隊列。

入度數組判斷哪些元素入隊。

如下:

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);
}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章