背景
開學了,好開心啊!
周末好不容易寫篇博客,搞長一點把。。。
最短路概念
這周花了點時間研究最短路問題,那么什么是最短路呢?
摘自百度百科:
最短路問題(short-path problem)是網絡理論解決的典型問題之一,可用來解決管路鋪設、線路安裝、廠區布局和設備更新等實際問題。基本內容是:若網絡中的每條邊都有一個數值(長度、成本、時間等),則找出兩節點(通常是源節點和阱節點)之間總權和最小的路徑就是最短路問題。 [1]
我了解的最短路算法有四種——floyd,Dijkstra,Bellman-Ford,SPFA,四種算法各有各的特點,適用的圖也有不同。
松弛操作
在學習最短路算法前,首先要了解松弛操作,這是所有最短路算法的核心,即是對於一條邊(u,v,w),比較以中間點k或不以k當中間點時的最短路那個最小,選擇小的那條來更新最短路:
if(dist[u]+w<dist[v])dist[v]=dist[u]+w;
(A)Floyd算法 時間復雜度O(n3)
1)基於動態規划,本質上是一個三維的DP
2)與其他算法不同,floyd是一個多源最短路徑算法,即經過一次floyd后能求出任意兩點間的最短路
3)基本思想:dist[i][j][k]是i到j的只以1-k為中間路點的最短路,則dist[i][j][k]=min(dist[i][j][k-1],dist[i][k][k-1]+dist[k][j][k-1]);即選擇不以k或以k當中間點時的最小的dist為dist[i][j][k]
4)具體實現:
▶初始化:dist[s][v]=l(s,v)【如果存在邊(s,v)】
▶進行松弛操作
3)核心代碼
for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(dist[i][k]+dist[k][j]<dist[i][j]) dist[i][j]=dist[i][k]+dist[k][j];
(B)Dijkstra算法 時間復雜度:堆優化:O((n+m)*log m) 無優化:O(nm)
1)只適用於無負邊權的圖(有負邊要么TLE要么WA)
2)基本思想:不斷用已更新的邊去更新他所連接的邊。
3)具體實現:
▶初始化:dist[s]=0;其余dist[i]=INF;然后把s點加入待處理隊列
▶更新:每次以隊首節點為基礎更新
4)
Q :如果要求具體的最短路徑怎么辦?
A :在更新時如果可以更新就用一個path數組存儲這個點是從那個點更新來的,輸出時帶着輸出就可以了
5)
Q:怎么寫堆優化?
A:用一個優先隊列(原理是小根堆)存儲每次更新了的點,依次用這些點去更新這個點所連的邊,更新完后就讓他出隊
6)代碼:
#include <bits/stdc++.h> #define re register using namespace std; inline int read() { int X=0,w=1; char c=getchar(); while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); } while (c>='0'&&c<='9') X=(X<<3)+(X<<1)+c-'0',c=getchar(); return X*w; } struct Edge { int v,w,nxt; }; Edge e[500010]; int head[100010],cnt=0; inline void addEdge(int u,int v,int w) { e[++cnt].v=v; e[cnt].w=w; e[cnt].nxt=head[u]; head[u]=cnt; } int n,m,s; int dis[100010]; struct node { //堆節點 int u,d;//存儲每次更新到的點和它的dist //重載運算 bool operator <(const node& rhs) const { return d>rhs.d; } }; inline void Dijkstra() { for (re int i=1;i<=n;i++) dis[i]=2147483647; dis[s]=0; priority_queue<node> Q; //堆 Q.push((node){s,0}); while (!Q.empty()) { node fr=Q.top(); Q.pop(); int u=fr.u,d=fr.d; if (d!=dis[u]) continue; for (re int i=head[u];i;i=e[i].nxt) { int v=e[i].v,w=e[i].w; if (dis[u]+w<dis[v]) { dis[v]=dis[u]+w; Q.push((node){v,dis[v]}); } } } } int main() { n=read(),m=read(),s=read(); for (re int i=1;i<=m;i++) { int X=read(),Y=read(),Z=read(); addEdge(X,Y,Z); } Dijkstra(); for (re int i=1;i<=n;i++) printf("%d ",dis[i]); return 0; }
(C)Bellman-ford算法 時間復雜度:O(mn)
1)基於動態規划
2)可以用來判負環
3)如果沒有負環,至多更新n-1輪或者某輪沒有更新即可出解
4)判負環:如果更新第n-1環之后還有邊能更新,就有負環
5)具體實現:
1)dist[s]=0;其余=INF
2)更新n-1輪,其中每輪:
1)對於每一條邊,如果if(dist[u]+w<dist[v])dist[v]=dist[u]+w;並且把updated標記改為有更新
2)更完每條邊后,如果updateed標記是沒更新,就break,因為這時已經更新結束,並且這樣肯定沒負環
3)最后把updated改為0,進行下一輪
4)再單獨更新一輪,如果這單獨一輪中有更新,就判為有負環
6)生動形象的演示:
#include<cstdio> #define INF 2147483647 #define MAX 10010 struct Edge{ int u,v,w; }edge[MAX]; int n,m,s,t,dist[MAX]; void BellmanFord(){ int updated=0; for(int j=1;j<=n-1;j++){ for(int i=1;i<=m;i++) if(dist[edge[i].u]+edge[i].w<dist[edge[i].v]){ dist[edge[i].v]=dist[edge[i].u]+edge[i].w; updated=1; } if(!updated)break; updated=0; } for(int i=1;i<=m;i++) if(dist[edge[i].u]+edge[i].w<dist[edge[i].v]){ printf("Orz"); return; } printf("%d",dist[t]); } int main(){ scanf("%d%d%d%d",&n,&m,&s,&t); int X,Y,Z; for(int i=1;i<=m;i++){ scanf("%d%d%d",&X,&Y,&Z); edge[i].u=X; edge[i].v=Y; edge[i].w=Z; } BellmanFord(); return 0; }
(D)SPFA 常被卡 時間復雜度:最壞:O(nm) 一般:不知道
1)關於SPFA,他死了
2)原因是在NOI中Dijkstra和SPFA這對難兄難弟經常其中一個被卡數據導致不能通過,而SPFA最經常
3)和Dijkstra有點像,我有時候都分不清
3)SPFA也是用一個隊列(不是優先隊列),第一步從s點開始,s入隊,對於隊中的每一個元素,廣度遍歷其所有出邊(u,v,w),並對其進行松弛,如果松弛可以進行,就讓v入隊,搜算完成后,讓u出隊,進行下一輪搜索,直至隊列空
4)是bellmanford的優化
5)具體實現:
1)初始化:dist[s]=0;其余=INF;
2)從s開始搜索,進行松弛,直至隊列空
6)代碼:
#include <bits/stdc++.h> #define ll long long #define MAXM 500005 #define MAXN 10005 using namespace std; int n,m,s,sz,st,en; int Q[4000005],f[MAXN],to[MAXM<<1],nex[MAXM<<1],v[MAXM<<1],las[MAXN]; bool inq[MAXN]; int inline read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void ins(int x,int y,int z) { sz++;to[sz]=y;v[sz]=z;nex[sz]=las[x];las[x]=sz; } void spfa() { memset(f,127/2,sizeof(f)); f[s]=0;Q[st=en=1]=s; while (st<=en) { int x=Q[st++]; inq[x]=false; for (int i=las[x];i;i=nex[i]) if (f[x]+v[i]<f[to[i]]) { f[to[i]]=f[x]+v[i]; if (!inq[to[i]]) { inq[to[i]]=true; Q[++en]=to[i]; } } } } int main() { n=read(),m=read(),s=read(); for (int i=1;i<=m;i++) { int x=read(),y=read(),z=read(); ins(x,y,z); } spfa(); for (int i=1;i<=n;i++) printf("%d%c",f[i]>1e9?2147483647:f[i],i==n?'\n':' '); return 0; }
以上就是關於DAG最短路的算法,各種算法各有各的優缺點,主要體現在時空復雜度上,要謹慎使用
關於圖論的內容的話再寫一個拓補排序就不寫了(表示圖論從暑假看到現在已經看吐了)
求推薦.