最短路徑算法簡介
最短路徑算法是在圖中求兩點(或多點)之間的最短路徑,我們最常見的最短路徑算法有四種:Bellman-ford、Dijkstra、SPFA、Floyd。
Bellman-ford算法可以用於有負邊權的圖,如果途圖中有負環,算法也可以檢驗出來,時間復雜度為O(VE)。
Dijkstra算法只能用於邊權為正的圖中,時間復雜度為O(n^2)。
SPFA算法是Bellman-ford算法的優化算法,和Bellman-ford算法應用差不多,而且可以用鄰接表和隊列優化,時間復雜度為O(KE),SPFA的時間復雜度有常數,有的比賽可能會卡常,所以建議求圖上最短路的時候用Dijkstra算法。
Floyd可以用於有負權的圖中,即使有負環,算法也可以檢測出來,可以求任意點的最短路徑,有向圖和無向圖的最小環和最大環。時間復雜度O(n^3)。
這篇博客主要介紹Dijkstra算法
Dijkstra算法
Dijkstra算法是我求圖上單元最短路最常用的方法,他可以求出從圖上一個點到其他所有點的最短路徑,他可以用優先隊列(堆)優化,我們先來介紹一下Dijkstra算法不優化的做法。
Dijkstra算法(不優化)
大體思路:先遍歷一下還沒有在最短路中的點,選出一個距離 已經在最短路集合中的點 距離最近的點,並把它加入到最短路中,並且更新所有點的最短路,直到所有的點都加入到最短路中。
下面我們來詳細模擬一下Dijkstra算法的實現過程:
變量名:d[i]表示從起始點到節點i的最短路徑 v[i]表示節點i是否被訪問過
初始化:所有最短路賦值為+∞,並且使d[1]=0,v[1]=1,因為任何節點到它本身的距離為0,標記1被訪問過。
我們來求一下下面這張圖從①到其他節點的最短路徑
步驟①:遍歷與節點1相連的所有節點,找到距離最近的一個,把這個節點標記為訪問過,並更新最短路。因為新加入一條邊,可能有很多的最短路會發生改變,所以要更新 所有與最短路徑包含的點 相連的點的最短路。由圖可知,10>5>1,所以我們把節點4標記為訪問過,v[4]=1,並且更新最短路——>d[4]=1。
步驟②:遍歷與 最短路包含的點 相連的節點,找到距離最近的。2<5<10,所以把節點3加入最短路,並且標記為訪問過—— d[3]=3,v[3]=1。
步驟③:遍歷與 最短路包含的點 相連的節點,找到距離最近的。5<6<10,所以把節點2加入最短路,並且標記為訪問過—— d[2]=5,v[2]=1。
步驟④:遍歷與 最短路包含的點 相連的節點,找到距離最近的。6<10,所以把節點5加入最短路,並且標記為訪問過—— d[5]=9,v[5]=1。
這樣,這張圖的最短路徑就求出來了,是不是很好理解呢!
下面是代碼
#include<iostream> #include<cstdio>
#define MAXN 2147483647
using namespace std; int n,m,s; struct G{ int u; int v; int w; int next; }e[500001]; int head[500001]; int d[500001]; int read(){ int num=0; char ch=getchar(); while(ch<'0' || ch>'9') ch=getchar(); while(ch>='0' && ch<='9'){ num=num*10+ch-'0'; ch=getchar(); } return num; } bool v[500001]; int main(){ int i,j; n=read(); m=read(); s=read(); for(i=1;i<=m;i++){ int a=read(),b=read(),c=read(); e[i].u=a; e[i].v=b; e[i].w=c; e[i].next=head[a]; head[a]=i; } for(i=1;i<=n;i++) d[i]=MAXN; //初始化
d[s]=0; for(i=1;i<=n;i++){ int k=0,minn=MAXN; for(j=1;j<=n;j++) //尋找距離最小的點
if(minn>d[j] && !v[j]){ minn=d[j]; k=j; } v[k]=1; //標記為訪問過
for(j=head[k];j!=0;j=e[j].next) if(!v[e[j].v] && d[e[j].v]>d[e[j].u]+e[j].w) //更新節點
d[e[j].v]=d[e[j].u]+e[j].w; } for(i=1;i<=n;i++) cout<<d[i]<<" "; return 0; }
(存圖的時候,建議用鄰接表,方便,而且速度比數組快)
到此,我們最基礎的Dijkstra算法就已經講完了,下面我們來講一下堆優化。
Dijkstra算法堆優化
堆優化用到了優先隊列(priority_queue),如果不會優先隊列,可以先看一下這個博客學習一下 https://blog.csdn.net/c20182030/article/details/70757660。
我們來回顧一下算法的核心部分:先找最小距離,再更新。在不優化的時候,我們是每次通過循環來尋找最小距離的,這樣的話就會有很多不必要的時間浪費掉,所以我們想到了用優先隊列優化,因為優先隊列會自動按照優先度排序,這樣就會省掉尋找最小距離的循環,從而節省一部分時間。
然后我們再想一想優先隊列里存放什么東西。我們來看一下不優化之前,在搜索最小距離的時候需要記錄的東西——最小距離和節點的編號,所以我們在優先隊列里面就放最小距離和節點的編號。為方便操作,我們就申請一個struct或者pair的優先隊列,struct的話就申請兩個變量,而pair的話(不了解pair的可以看一下這個博客 https://www.cnblogs.com/lvchaoshun/p/7769003.html),就在第一個位置里放距離,第二個位置里放id,因為優先隊列默認先按照第一關鍵字排序,我們的目的就是要快速的找到最小距離,所以按照距離排序。優先隊列默認從大到小排序,所以我們要使優先隊列從小到大排序,或者是在加入新的二元組的時候,把距離變成負的。
其余的部分和不優化差別不是很大,下面給出代碼:
#include<bits/stdc++.h>
using namespace std; typedef pair<int,int> pi; const int MAXN=2e6+10; struct EDGE{ int u; int v; int w; int next; }edge[MAXN]; int head[MAXN],tot; int t,ans; int n,m; char a[1000][1000]; int dis[MAXN]; bool vis[MAXN]; priority_queue<pi,vector<pi>,greater<pi> > q; pi tmp; int read(){ int x=0; char c=getchar(); while(c<'0' || c>'9') c=getchar(); while(c>='0' && c<='9'){ x=x*10+c-'0'; c=getchar(); } return x; } void add(int x,int y,int z){ edge[++tot].u=x; edge[tot].v=y; edge[tot].w=z; edge[tot].next=head[x]; head[x]=tot; edge[++tot].u=y; edge[tot].v=x; edge[tot].w=z; edge[tot].next=head[y]; head[y]=tot; } int main(){ register int i,j; int id,x,nd; t=read(); while(t--){ for(i=1;i<=tot;i++) edge[i].next=edge[i].u=edge[i].v=edge[i].w=head[i]=0; tot=0; memset(dis,0x3f,sizeof dis); memset(vis,0,sizeof vis); n=read(),m=read(); for(i=1;i<=n;++i) for(j=1;j<=m;++j){ cin>>a[i][j]; if(a[i][j]!='/') add((i-1)*(m+1)+j,i*(m+1)+j+1,0),add((i-1)*(m+1)+j+1,i*(m+1)+j,1); else add((i-1)*(m+1)+j,i*(m+1)+j+1,1),add((i-1)*(m+1)+j+1,i*(m+1)+j,0); } if((m+n)%2!=0){ printf("NO SOLUTION\n"); continue; } q.push(make_pair(0,1)); while(!q.empty()){ tmp=q.top(); q.pop(); id=tmp.second,nd=tmp.first; if(vis[id]) continue; vis[id]=1; for(i=head[id];i;i=edge[i].next){ if(vis[edge[i].v]) continue; if(dis[edge[i].v]>nd+edge[i].w){ dis[edge[i].v]=nd+edge[i].w; q.push(make_pair(dis[edge[i].v],edge[i].v)); } } } printf("%d\n",dis[(n+1)*(m+1)]); } return 0; }
(洛谷原題鏈接https://www.luogu.org/problemnew/show/P2243)
(本博客部分內容摘自:https://blog.csdn.net/qq_36386435/article/details/77403223)