一.Dijkstra 算法
dijkstra算法適用於邊權為正的情況,求單源最短路,適用於有向圖和無向圖
模板偽代碼:
清除所有點的標號
設d[0]=0,其余d[i]=INF;
循環n次{
在所有未標記的節點中,尋找d[i]最小的點x
給x做標記
對於從x出發的所有邊(x,y)更新d[y]=min(d[y],d[x]+w[x,y]);
}
memset(v,0,sizeof(v)); for(int i=0;i<n;++i) d[i]=(i==0?0:INF); for(int i=0;i<n;++i) { int x,m=INF; for(int j=0;j<n;++j) if(!visit[j]&&d[j]<m) { m=d[j]; x=j; } visit[x]=1; for(int j=0;j<n;++j) d[j]=min(d[j],d[x]+w[x][j]); }
簡單說一下dijkstra的優化:
1.儲存結構上:鄰接矩陣是很占空間的(眾所周知),所以我們一般采用鄰接表或者邊表
2.堆優化:因為在dijkstra中有循環n次尋找最小dict的過程,我們可以維護一個小根堆來實現,也就把時間復雜度從n^2降到了n*(logn+E)。
優化后的dijkstra,自己寫的:
/* 建圖用的鄰接表,復雜度O(E*logE) */ struct pnode { int num; int len; pnode() {} pnode(int a, int b) : num(a), len(b) {}//初始化結構體用的,把a復制給num,把b復制給len; bool operator < (const pnode tmp) const { return len > tmp.len; } }; int dis[N]; bool vis[N]; int n; void dijkstra(int s) { priority_queue<pnode> q; q.push(pnode(s, 0)); pnode u; int v, i, res = inf; for(i = 0; i <= n; ++i) dis[i] = inf, vis[i] = false; dis[s] = 0; while(!q.empty()) { u = q.top(); q.pop(); if(u.len != dis[u.num]) continue;/*這是應對優先隊列中的重復入隊的點,只要最新的那個點就可以了*/ if(vis[u.num]) continue; vis[u.num] = true; for(i = head[u.num]; i != -1; i = g[i].next) { v = g[i].to; if(dis[v] > u.len + g[i].val) { dis[v] = u.len + g[i].val; q.push(pnode(v, dis[v])); } } } }
二.Bellman-Ford的優化(也就是SPFA,直接看三吧)
三.SPFA模板及SPFA的優化
1.普通SPFA模板(隊列化的Bellman-Ford算法):
int visit[N],dis[N]; bool SPFA(int s) { queue<int>q; memset(dis,127,sizeof(dis)); memset(visit,false,sizeof(visit));
memset(cnt,0s,sizeof(cnt)); dis[s]=0; visit[s]=true; q.push(s); while(!q.empty()) { int k=q.front(); q.pop(); visit[k]=false; for(int i=head[k];i;i=edge[i].last)/*邊表*/ { if(dis[k]+edge[i].w<dis[edge[i].v]) { dis[edge[i].v]=dis[k]+edge[i].w; if(!visit[edge[i].v]) { q.push(edge[i].v); visit[edge[i].v]=true;
if(++cnt[edge[i].v]>n) /*如果某一個點的入隊次數超過了n次,說明存在負環,返回false*/
return false; } } } } return true;/*安全跑完了,不存在環*/ }
2.SPFA的優化
SPFA算法有兩個優化策略SLF和LLL——SLF:Small Label First 策略,設要加入的節點是j,隊首元素為i,若dist(j)<dist(i),則將j插入隊首,否則插入隊尾; LLL:Large Label Last 策略,設隊首元素為i,隊列中所有dist值的平均值為x,若dist(i)>x則將i插入到隊尾,查找下一元素,直到找到某一i使得dist(i)<=x,則將i出隊進行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高約 50%。 在實際的應用中SPFA的算法時間效率不是很穩定,為了避免最壞情況的出現,通常使用效率更加穩定的Dijkstra算法。實際上dijkstra算法+heap優化后是一定快於一般SPFA的,而且更加穩定。
1)SPFA的SLF優化,(很簡單的,只要使用雙端隊列就可以實現了)。

#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #include <deque> using namespace std; const int N=501; const int NN=100001; const int inf=0x7fffffff; int n,nu; typedef struct node { int adj,val; struct node *next; }; node node[NN],*p[N]; int SPFA() { deque<int> qu; int x,i,a,b; int vis[N],dis[N],num[N]; struct node *head[N]; for(i=1;i<=n;i++) { vis[i]=0; num[i]=0; dis[i]=inf; head[i]=p[i]; } dis[1]=0; vis[1]=1; num[1]++; qu.push_back(1); while(!qu.empty()) { x=qu.front(); qu.pop_front(); vis[x]=0; head[x]=p[x]; while(head[x]) { a=head[x]->adj; b=head[x]->val; if(dis[a]>dis[x]+b) { dis[a]=dis[x]+b; if(!vis[a]) { vis[a]=1; num[a]++; if(num[a]>=n) return 1; if(!qu.empty()) { if(dis[a]>dis[qu.front()]) qu.push_back(a); else qu.push_front(a); } else qu.push_back(a); } } head[x]=head[x]->next; } } return 0; } int main() { int t,i,m,w,a,b,c; scanf("%d",&t); while(t--) { memset(node,0,sizeof(node)); memset(p,0,sizeof(p)); nu=0; scanf("%d%d%d",&n,&m,&w); for(i=0;i<m;i++) { scanf("%d%d%d",&a,&b,&c); node[nu].adj=b; node[nu].val=c; node[nu].next=p[a]; p[a]=&node[nu]; nu++; node[nu].adj=a; node[nu].val=c; node[nu].next=p[b]; p[b]=&node[nu]; nu++; } for(i=0;i<w;i++) { scanf("%d%d%d",&a,&b,&c); node[nu].adj=b; node[nu].val=-c; node[nu].next=p[a]; p[a]=&node[nu]; nu++; } if(SPFA()) puts("YES"); else puts("NO"); } return 0; }

#include<iostream> using namespace std; #include<deque> #include<cstdio> #include<cstring> int n,m; #define N 1001 struct Edge{ int u,v,w,last; }edge[N]; int head[N]; int dict[N]; bool visit[N]; void input() { scanf("%d%d",&n,&m);/*n個點,m條邊*/ for(int i=1;i<=m;++i) { scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w); edge[i].last=head[edge[i].u]; head[edge[i].u]=i; } } bool SPFA() { deque<int>q; memset(dict,127,sizeof(dict)); memset(visit,false,sizeof(visit)); dict[1]=0; visit[1]=true;; int cnt[N]; memset(cnt,0,sizeof(cnt));/*判斷有無環的標志*/ ++cnt[1]; while(!q.empty()) { int k=q.front(); q.pop_front(); visit[k]=false;/*取出后,不要忘記標志位*/ for(int l=head[k];l;l=edge[l].last)/*邊表*/ { int p=edge[l].v; if(dict[p]>dict[k]+edge[l].w) { dict[p]=dict[k]+edge[l].w; ++cnt[p]; if(cnt[p]>n) return true;/*如果某個點的入隊次數超過了n,那么一定存在環*/ if(!visit[p]) { visit[p]=true; if(!q.empty())/*這就是SLF Small Label First 策略.的核心,把將要入隊的元素的dict與隊首元素相比較,如果將要入隊的元素的dict大的話,就放在隊尾,否則就放在隊首 */ { if(dict[p]>dict[q.front()])/*這樣可以保證始終用dict小的更新,也節約了時間*/ q.push_back(p); else q.push_front(p);/*所以必須使用雙端隊列*/ } else q.push_back(p);/*不要忘記考慮隊列為空的情況*/ } } } } return false; } int main() { input(); if(SPFA()) printf("circle"); else{ for(int i=1;i<=n;++i) printf("%d ",dict[i]); } return 0; }
2.SPFA的LLL優化
在網上實在沒找到可靠的。
3.SPFA的DFS優化:
在很多的題目中,SPFA都是用BFS來實現的,對於隨機圖來說,BFS的速度會遠大於DFS,但是對於某些特殊的圖結構來說,DFS也是一個很好的選擇
例如 1):題目中要求在最短的時間內,判斷有無環,DFS明顯會比BFS快(例題是POj上一個判斷單詞接龍的題目)
2):對於網格型的圖,DFS的速度比BFS快
模板:
void SPFA(int k) { flag[k]=true; for(int l=head[k];l;l=edge[l].last) { int v=edge[l].v;/*找出第一個可以被更新的點*/ if(dis[v]>dis[k]+edge[l].w) { dis[v]=dis[k]+edge[l].w; if(!flag[v]) { SPFA(v);/*接着深搜下去*/ } else /*這表明從某個點開始DFS,結果又搜到了這個點,說明存在負權回路*/ { printf("cycle"); return; } } } flag[k]=false;/*不要忘記再把k設為false,為的是讓他能夠重復入隊*/ }
四,Floyd算法模板(沒有任何優化,就是鄰接矩陣和n^3,一般情況單源最短路是絕對不會用的)
for(int k=1;k<=n;++k)/*注意這個k必須在最外層循環才行*/ for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);