藍橋杯 算法訓練 最短路


問題描述

給定一個n個頂點,m條邊的有向圖(其中某些邊權可能為負,但保證沒有負環)。請你計算從1號點到其他點的最短路(頂點從1到n編號)。

輸入格式

第一行兩個整數n, m。

接下來的m行,每行有三個整數u, v, l,表示u到v有一條長度為l的邊。

輸出格式
共n-1行,第i行表示1號點到i+1號點的最短路。
樣例輸入
3 3
1 2 -1
2 3 -1
3 1 2
樣例輸出
-1
-2
數據規模與約定

對於10%的數據,n = 2,m = 2。

對於30%的數據,n <= 5,m <= 10。

對於100%的數據,1 <= n <= 20000,1 <= m <= 200000,-10000 <= l <= 10000,保證從任意頂點都能到達其他所有頂點。

我在網上搜索了最短路算法,發現基本有4種。

1.floyd 算法   2.dijkstra算法   3.bellman算法   4.spfa算法

floyd算法時間復雜度高,dijkstra算法不能計算帶負邊權的圖,故均不考慮。

spfa算法是bellman的一種優化,所以決定從簡單的先開始。

bellman實現的代碼如下:

#include <iostream>#include<string.h>
using namespace std;

int dis[20005],u[200005],v[200005],w[200005];//dis[i]代表第一點到第i點距離,u[i],v[i],w[i]分別代表第i條邊的起始點,終點和長度。
int main()
{
int m,n;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u[i]>>v[i]>>w[i];
}
memset(dis,10001,sizeof(dis));
dis[1]=0;
for(int k=1;k<n;k++)//最極端的情況下,一個點要松弛n-1次
{
for(int i=1;i<=m;i++)//枚舉所有的條邊
{
if(dis[v[i]]>dis[u[i]]+w[i])
//如果第一點到v[i]點的距離大於第一點到u[i]再加上u[i]到v[i]的距離
dis[v[i]]=dis[u[i]]+w[i];//說明dis[v[i]]的值可以更小,所以更新dis[v[i]]的值,專業的說法就是進行了一次松弛操作
}
}
for(int i=2;i<=n;i++)
cout<<dis[i]<<endl;
}

這段代碼很簡 單,最關鍵的地方在兩層for循環的那部分。算法的時間復雜度顯然是O(mn),最后提交超時。
代碼的缺點在於不是所有的點都要松弛n-1次,並且絕大部分的點松弛的次數都遠小於n-1,這就造成了大量的不必要的運算。

SPFA(Shortest Path Faster Algorithm)算法是求單源最短路徑的一種算法,它是Bellman-ford的隊列優化,它是一種十分高效的最短路算法。

SPFA的復雜度大約是O(kE),k是每個點的平均進隊次數(一般的,k是一個常數,在稀疏圖中小於2)。

但是,SPFA算法穩定性較差,在稠密圖中SPFA算法時間復雜度會退化。

實現方法:建立一個隊列,初始時隊列里只有起始點,在建立一個表格記錄起始點到所有點的最短路徑(該表格的初始值要賦為極大值,該點到他本身的路徑賦為0)。然后執行松弛操作,用隊列里有的點去刷新起始點到所有點的最短路,如果刷新成功且被刷新點不在隊列中則把該點加入到隊列最后。重復執行直到隊列為空。

此外,SPFA算法還可以判斷圖中是否有負權環,即一個點入隊次數超過N

#include <iostream>#include<string.h>#include<queue> using namespace std;struct edge{	int to,val,next;}e[200100];//e[i].to,e[i].val,e[i].next分別表示第i條邊的終點,權值,和另(上)一條於此邊相同起點的邊的編號int m,n,head[21000],dis[21000];//head[i]表示當前以i為起點的邊的編號,dis[i]表示1號點到i號點的距離void add(int from,int to,int val,int len)//添加邊,參數分別代表此邊的起點,終點,權值,還有此邊的編號{	e[len].to=to;	e[len].val=val;	e[len].next=head[from];//此邊的起點是from,而head[from]保存上一條起點是from的邊的編號	head[from]=len;//添加了這條邊后,現在,最新的以from為起點的邊編號就是len}void spfa(){	int s;	queue <int> q;	int visit[21000];//visit[i]==0代表第i點不在隊列中,visit[i]==1代表已在隊列中	for(int i=1;i<=n;i++)		{			dis[i]=99999999;		}//將權值初始化為最大值	memset(visit,0,sizeof(visit));//初始沒有點在隊列中	dis[1]=0;	q.push(1);	visit[1]=1;//現在把第一個點扔進隊列	while(!q.empty())//如果隊列不空則重復執行以下操作	{		int t=q.front();//取隊列的第一個元素		q.pop();		visit[t]=0;//t現在不在隊列里		for(int i=head[t];i!=-1;i=e[i].next)//枚舉所有以t為起點的邊		{			s=e[i].to;//s為所枚舉的邊的終點			if(dis[s]>dis[t]+e[i].val)//嘗試松弛s點			{				dis[s]=dis[t]+e[i].val;			    if(visit[s]==0)			    {				 q.push(s);			     	visit[s]=1;			    }//如果成功松弛了s點,把s點扔進隊列			}		}	}	}int main (){	cin>>n>>m;	int from,val,to;	int len=1;	memset (head,-1,sizeof(head));	for(int i=0;i<m;i++)	{		cin>>from>>to>>val;		add(from,to,val,len);		len++;	}	spfa();	for(int i=2;i<=n;i++)	{		cout<<dis[i]<<endl;	}}





注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com