這里給大家介紹三種最短路常用算法:
floyd(O(n^3))、dijkstra(O(nlogn))、SPFA(O(KE))(k是進隊列次數)
其實還有一個Bellman-Ford(O(nm))算法,但由於不常用而且SPFA是這個算法的改進版本,在這里就不列舉了
floyd:效率較低,只有70分
具體思路:將所有節點的距離都存在一個數組里,由於要枚舉所有的兩兩組合以及每一個組合的“中轉點”,再進行松弛操作
在求單源最短路徑的時候就會浪費許多空間,但在求多源最短路時,復雜度仍是O(n^3)使用很廣
#include<bits/stdc++.h>
using namespace std;
#define inf 1234567890
#define maxn 10005
inline int read()
{
int x=0,k=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*k;
}//快讀
int a[maxn][maxn],n,m,s;
inline void floyd()
{
for(int k=1;k<=n;k++)
//這里要先枚舉k(可以理解為中轉點)
{
for(int i=1;i<=n;i++)
{
if(i==k||a[i][k]==inf)
{
continue;
}
for(int j=1;j<=n;j++)
{
a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
//松弛操作,即更新每兩個點之間的距離
//松弛操作有三角形的三邊關系推出
//即兩邊之和大於第三邊
}
}
}
}
int main()
{
n=read(),m=read(),s=read();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
a[i][j]=inf;
}
}
//初始化,相當於memset(a,inf,sizeof(a))
for(int i=1,u,v,w;i<=m;i++)
{
u=read(),v=read(),w=read();
a[u][v]=min(a[u][v],w);
//取min可以對付重邊
}
floyd();
a[s][s]=0;
for(int i=1;i<=n;i++)
{
printf("%d ",a[s][i]);
}
return 0;
}
dijkstra:對於無負邊的情況下可以達到O(nlogn)且很難被卡
具體思路:Dijkstra是基於一種貪心的策略,首先用數組dis記錄起點到每個結點的最短路徑,再用一個數組保存已經找到最短路徑的點
然后,從dis數組選擇最小值,則該值就是源點s到該值對應的頂點的最短路徑,並且把該點記為已經找到最短路
此時完成一個頂點,再看這個點能否到達其它點(記為v),將dis[v]的值進行更新
不斷重復上述動作,將所有的點都更新到最短路徑
這種算法實際上是O(n^2)的時間復雜度,但我們發現在dis數組中選擇最小值時,我們可以用一些數據結構來進行優化。線段樹?平衡樹?
其實我們可以用STL里的堆來進行優化,堆相對於線段樹以及平衡樹有着常數小,碼量小等優點,並且堆的一個妙妙的性質就是可以在nlogn的時限內滿足堆頂是堆內元素的最大(小)值,之不正是我們要的嘛?
以下是用堆優化dijkstra代碼:
#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
#define maxm 500005
#define INF 1234567890
inline int read()
{
int x=0,k=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*k;
}
struct Edge
{
int u,v,w,next;
}e[maxm];
int head[maxn],cnt,n,m,s,vis[maxn],dis[maxn];
struct node
{
int w,now;
inline bool operator <(const node &x)const
//重載運算符把最小的元素放在堆頂(大根堆)
{
return w>x.w;//這里注意符號要為'>'
}
};
priority_queue<node>q;
//優先隊列,其實這里一般使用一個pair,但為了方便理解所以用的結構體
inline void add(int u,int v,int w)
{
e[++cnt].u=u;
//這句話對於此題不需要,但在縮點之類的問題還是有用的
e[cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
//存儲該點的下一條邊
head[u]=cnt;
//更新目前該點的最后一條邊(就是這一條邊)
}
//鏈式前向星加邊
void dijkstra()
{
for(int i=1;i<=n;i++)
{
dis[i]=INF;
}
dis[s]=0;
//賦初值
q.push((node){0,s});
while(!q.empty())
//堆為空即為所有點都更新
{
node x=q.top();
q.pop();
int u=x.now;
//記錄堆頂(堆內最小的邊)並將其彈出
if(vis[u]) continue;
//沒有遍歷過才需要遍歷
vis[u]=1;
for(int i=head[u];i;i=e[i].next)
//搜索堆頂所有連邊
{
int v=e[i].v;
if(dis[v]>dis[u]+e[i].w)
{
dis[v]=dis[u]+e[i].w;
//松弛操作
q.push((node){dis[v],v});
//把新遍歷到的點加入堆中
}
}
}
}
int main()
{
n=read(),m=read(),s=read();
for(int i=1,x,y,z;i<=m;i++)
{
x=read(),y=read(),z=read();
add(x,y,z);
}
dijkstra();
for(int i=1;i<=n;i++)
{
printf("%d ",dis[i]);
}
return 0;
}
SPFA:考場慎用,在毒瘤數據面前可能退化到O(nm)
具體思路:這里用的是STL隊列,首先用數組dis記錄起點到每個結點的最短路徑,用鄰接表來存儲圖,用vis數組記錄當前節點是否在隊列中
具體操作為:用隊列來保存待優化的結點(類似於BFS),優化時每次取出隊首結點,並且用隊手節點來對最短路徑進行更新並進行松弛操作
如果要對所連點的最短路徑需要更新,且改點不在當前的隊列中,就將改點加入隊列
然后不斷進行松弛操作,直至隊列空為止。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0,k=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*k;
}
#define maxn 10005
#define maxm 500005
#define inf 1234567890
int n,m,s,tot,dis[maxn],head[maxn];
bool vis[maxn];
struct Edge
{
int next,to,w;
}h[maxm];
void add(int u,int v,int w)
{
h[++tot].next=head[u];
h[tot].to=v;
h[tot].w=w;
head[u]=tot;
}
//上面和dijkstra算法基本上一樣
queue<int> q;
//隊列優化
inline void spfa()
{
for(int i=1; i<=n; i++)
{
dis[i]=inf;
//賦初值
}
int u,v;
q.push(s);
dis[s]=0;
//將起點的值負為0
vis[s]=1;//這句話可加可不加,因為循環的時候vis[s]又會被賦為0
while(!q.empty())
//當隊列里沒有元素的時候,那就已經更新了所有的單源最短路徑
{
u=q.front();
//將隊手節點記錄並彈出隊首節點
q.pop();
vis[u]=0;
for(int i=head[u];i;i=h[i].next)
//尋找與u相連的邊
{
v=h[i].to;
if(dis[v]>dis[u]+h[i].w)
{
dis[v]=dis[u]+h[i].w;
//松弛操作,和floyd比較相似
if(!vis[v])
{
//已經在隊列里的點就不用再進入了
vis[v]=1;
q.push(v);
}
}
}
}
}
int main(){
n=read(),m=read(),s=read();
for(int i=1,u,v,w;i<=m;i++)
{
u=read(),v=read(),w=read();
add(u,v,w);
}
spfa();
for(int i=1; i<=n; i++)
{
printf("%d ",dis[i]);
}
return 0;
}
彩蛋:
SPFA是寫最短路徑而不用堆優化的唯一的人。他身材很高大;青白臉色,皺紋間時常夾些傷痕;
一部亂蓬蓬的花白的胡子。穿的雖然是女裝,可是又臟又破,似乎十多年沒有補,也沒有洗。
他對人說話,總是滿口O(kE),叫人半懂不懂的。因為他姓S,別人便從描紅紙上的“Shortest Path Faster Algorithm”這半懂不懂的話里,替他取下一個綽號,叫作SPFA。
SPFA一到機房,所有寫代碼的人便都看着他笑,有的叫道,“SPFA,你又TLE了!”他不回答,對我說,“打1e5個結點,要2e5條邊。”便排出一條隊列。
他們又故意的高聲嚷道,“你一定又被出題人卡了!”SPFA睜大眼睛說,“你怎么這樣憑空污人清白……”“什么清白?我前天親眼見你被出題人卡到O(nm),吊着打。”
SPFA便漲紅了臉,額上的青筋條條綻出,爭辯道,“TLE不能算O(nm)……O(nm)!卡常數的事,能算O(nm)么?”接連便是難懂的話,什么“SPFA的復雜度是O(kE)”,什么“可以證明k一般小於等於2”之類。
引得眾人都哄笑起來;機房內外充滿了快活的空氣。
