單源最短路徑算法總結
這裏給大家介紹三種最短路常用算法:
floyd(O(n^3))、dijkstra(O(nlogn))、SPFA(O(KE))(k是進隊列次數,在沒有負環的情況下2)
其實還有一個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;
}
單源最短路徑算法總結