heap+dijkstra与SPFA都是单源最短路的高效算法,到底谁比较快一直各有各的说法。于是心血来潮自己测试了下。
测试工具:cena 0.6
系统: windows vista
CPU: T2130, 1.86Ghz
所有程序中,图用相同格式存储,输入,输出,数据都是静态分配
邻接表参考dd的dinic代码
Dijkstra1: 我写的的支持内部修改的heap,
复杂度O((V+E)logV)
Dijkstra2: STL的priority_queue,更新之后结点直接加进堆中而不是更新堆内的结点
复杂度: ???
Dijkstra3: 朴素的Dijkstra
复杂度O(V^2+E)
SPFA: 经典的bellmen-ford的优化,用的最常见的写法,自己写的queue
复杂度O(k*E), k接近2
USACO: usaco中butter一题的标程稍作修改,本质与Dijkstra1相似,
复杂度O((V+E)logV)
TEST 1:2000个点的完全图
输入输出用时:2.15-2.9秒, 平均:2.47
用时(除去平均输入输出时间)
单位:秒:
Dijkstra1: 0.13 0.10
Dijkstra2: 0.05 0.41
Dijkstra3: 0.21 0.18
SPFA: 1.04 0.97
USACO: 0.29 0.40
大规模密集图,大量数据的输入输出占用了大部分时间。单纯计算时间太短难以比较。几种dijkstra都有很好的表现,SPFA由于运行时间对于边数的依赖性,显得逊色不少。
对于Dijkstra1和Dijkstra2 因为是密集图,每次找到一个点后无论是内部更新还是重新插入结点,都要O(log n)的复杂度(虽然插入结点的期望复杂度为O(1),但是由于是重新插入结点,堆内同时存在的总结点数可能会达到E的数量级,增大了常数),工作量都是较大的,堆的优势没有体现出来。 相比之下,朴素的Dijkstra的表现相当不错。
TEST 2: 100000个结点的链状图
输入输出用时:0.10 – 0.30秒, 平均:0.17
用时(除去平均输入输出时间)
单位:秒:
Dijkstra1: 0.03 0.07
Dijkstra2: 0.04 0.07
Dijkstra3: >10 >10
SPFA: 0.03 0.00
USACO: 0.15 0.18
对于极端的链状图,SPFA无疑是最合适的了。每个结只进队一次,标准的O(E)。朴素的dijkstra对于这样的图就力不从心了:每次循环都过一遍结点,在松弛,然后发现每次O(V)的时间只松弛了一个点。。
Dijkstra2 由于堆内的结点总数最多有E的数量级,稀疏图里和V接近,劣势没有体现出来。
TEST 3: 20000个结点的稀疏图(每个点100条边)
输入输出用时:2.15-2.40 秒, 平均:2.30
用时(除去平均输入输出时间)
单位:秒:
Dijkstra1: 0.30 0.33
Dijkstra2: 0.39 0.57
Dijkstra3: 2.20 3.26
SPFA: 5.45 5.11
USACO: 0.27 0.33
普通的稀疏图,比TEST 2的密集。 Dijkstra+heap依然是最快的。
比较惊奇的结果是:SPFA居然会比朴素dijkstra还慢……有些出乎预料
原因还没想明白……
TEST 3: 5000个结点的较密集图(每个点500条边)
输入输出用时:2.44-2.99 秒, 平均:2.75
用时(除去平均输入输出时间)
单位:秒:
Dijkstra1: 0.30 0.32
Dijkstra2: 0.39 0.33
Dijkstra3: 0.40 0.40
SPFA: 1.04 1.04
USACO: 0.40 0.19
比较密集的图。SPFA的表现仍然不佳,Dijkstra+heap 任然是王道,朴素dijkstra的劣势逐渐缩小
总结:
Dijkstra1对于各种图都有良好表现,但是编程复杂度较高,需要准备模板。Dijkstra2速度也很不错,普遍略逊于Dijkstra1。Dijkstra3对于密集图有不错的表现。
SPFA表现不尽如人意。但是由于SPFA编程复杂度低,适用于各种图,也可以用来负权环,适合比赛使用。
编程复杂度:SPFA>naive dijkstra>STLheap+dijkstra>heap+dijkstra(越靠前越容易是写)
综合效率: heap+dijkstra>STLheap+dijkstra> SPFA>naive dijkstra
比赛的时候 STLheap+dijkstra 和 SPFA 是不错的选择
dijkstra1:
#include<iostream> using namespace std; struct MinBinaryHeap{ struct node{ int key; int value; inline bool operator<(const node &t)const{ return value < t.value; } }*array; int *location,heap_size; MinBinaryHeap(int size){ heap_size=0; array=new node[size+1]; location=new int[size]; } ~MinBinaryHeap(){ delete[] array; delete[] location; } inline void update(int loc,const node& x){ //put x into array[loc] array[loc]=x; location[x.key]=loc; } void per_down(int hole){ node tem=array[hole]; while(1){ int child=hole<<1; if(child > heap_size) break; if(child!=heap_size && array[child+1] < array[child]) child++; if(array[child] < tem){ update(hole,array[child]); hole=child; }else break; } update(hole,tem); } void per_up(int hole){ node tem=array[hole]; while(hole > 1){ if(tem < array[hole>>1]){ update(hole,array[hole>>1]); hole>>=1; }else break; } update(hole,tem); } void build_heap(int *values, int n){ heap_size=n; for(int i=1;i<=n;i++){ array[i].key=i-1; array[i].value=*(values++); location[array[i].key]=i; } for(int i=heap_size>>1;i>=1;i–) per_down(i); } pair<int,int> pop(){ pair<int,int> res=make_pair(array[1].key,array[1].value); array[1]=array[heap_size–]; per_down(1); return res;; } void decrease_to(int key,int value){ array[location[key]].value=value; per_up(location[key]); } }; const int maxn=100000,INF=0x11111111;; struct graph{ int node_num,allop_pt; struct edge{ int j,w; edge *next; }*g[maxn],*pool; graph(int node_num,int edge_num):node_num(node_num){ memset(g,0,sizeof(g)); pool=new edge[2*edge_num]; allop_pt=0; } ~graph(){ delete[] pool; } inline edge* new_edge(int j,int w,edge* next){ pool[allop_pt].j=j; pool[allop_pt].w=w; pool[allop_pt].next=next; return &pool[allop_pt++]; } inline void add_edge(int i,int j,int w){ g[i]=new_edge(j,w,g[i]); g[j]=new_edge(i,w,g[j]); } }; void dijkstra(graph& g,int source, int dis[]){ bool known[maxn]={0}; for(int i=0;i<g.node_num;i++)dis[i]=INF; dis[source]=0; MinBinaryHeap heap(g.node_num); heap.build_heap(dis,g.node_num); for(int k=0;k<g.node_num;k++){ pair<int,int> tem=heap.pop(); int i=tem.first; known[i]=true; for(graph::edge* e=g.g[i];e;e=e->next){ if(!known[e->j] && dis[e->j] > dis[i]+e->w){ dis[e->j]=dis[i]+e->w; heap.decrease_to(e->j,dis[e->j]); } } } }
dijkstra2:
#include<stdio.h> #include<queue> #include<string.h> #include<vector> using namespace std; const int maxn=100000,INF=0x11111111; struct graph{ int node_num,allop_pt; struct edge{ int j,w; edge *next; }*g[maxn],*pool; graph(int node_num,int edge_num):node_num(node_num){ memset(g,0,sizeof(g)); pool=new edge[2*edge_num]; allop_pt=0; } ~graph(){ delete[] pool; } inline edge* new_edge(int j,int w,edge* next){ pool[allop_pt].j=j; pool[allop_pt].w=w; pool[allop_pt].next=next; return &pool[allop_pt++]; } inline void add_edge(int i,int j,int w){ g[i]=new_edge(j,w,g[i]); g[j]=new_edge(i,w,g[j]); } }; struct node{ int v,dis; node(int v,int dis):v(v),dis(dis){} inline bool operator<(const node &b) const{ return dis>b.dis; } }; void dijkstra(graph& g,int source, int dis[]){ priority_queue<node> heap; heap.push(node(source,0)); bool known[maxn]={0}; for(int i=0;i<g.node_num;i++) dis[i]=INF; dis[source]=0; for(int i=0;i<g.node_num;i++){ int u,v,w; do{ node t=heap.top(); u=t.v; heap.pop(); }while(known[u]); known[u]=true; for(graph::edge *e=g.g[u];e;e=e->next){ v=e->j;w=e->w; if(!known[v] && dis[v]>dis[u]+w){ dis[v]=dis[u]+w; heap.push(node(v,dis[v])); } } } } int main(){ freopen(“in.txt”,”r”,stdin); freopen(“out.txt”,”w”,stdout); int dis[maxn]; int n,m,a,b,c; scanf(“%d%d”,&n,&m); graph G(n,m); while(m–){ scanf(“%d%d%d”,&a,&b,&c); G.add_edge(a,b,c); } dijkstra(G,0,dis); for(int i=0;i<n;i++)printf(“%d/n”,dis[i]); }
dijkstra3:
#include<stdio.h> #include<queue> #include<string.h> using namespace std; const int maxn=100000,INF=0x11111111;; struct graph{ int node_num,allop_pt; struct edge{ int j,w; edge *next; }*g[maxn],*pool; graph(int node_num,int edge_num):node_num(node_num){ memset(g,0,sizeof(g)); pool=new edge[2*edge_num]; allop_pt=0; } ~graph(){ delete[] pool; } inline edge* new_edge(int j,int w,edge* next){ pool[allop_pt].j=j; pool[allop_pt].w=w; pool[allop_pt].next=next; return &pool[allop_pt++]; } inline void add_edge(int i,int j,int w){ g[i]=new_edge(j,w,g[i]); g[j]=new_edge(i,w,g[j]); } }; int dis[maxn]; void dijkstra(graph &g,int source,int dis[]){ bool known[maxn]={0}; for(int i=0;i<g.node_num;i++)dis[i]=INF; dis[source]=0; for(int i=0;i<g.node_num;i++){ int u,v,w,min=INF; for(int i=0;i<g.node_num;i++) if(!known[i] && min>dis[i]){ min=dis[i]; u=i; } known[u]=true; for(graph::edge *e=g.g[u];e;e=e->next){ v=e->j,w=e->w; if(!known[v] && dis[v]>dis[u]+w) dis[v]=dis[u]+w; } } } int main(){ freopen(“in.txt”,”r”,stdin); freopen(“out.txt”,”w”,stdout); int n,m,a,b,c; scanf(“%d%d”,&n,&m); graph G(n,m); while(m–){ scanf(“%d%d%d”,&a,&b,&c); G.add_edge(a,b,c); } dijkstra(G,0,dis); for(int i=0;i<n;i++)printf(“%d/n”,dis[i]); }
SPFA:
#include<stdio.h> #include<string.h> using namespace std; const int maxn=100000,INF=0x11111111;; struct graph{ int node_num,allop_pt; struct edge{ int j,w; edge *next; }*g[maxn],*pool; graph(int node_num,int edge_num):node_num(node_num){ memset(g,0,sizeof(g)); pool=new edge[2*edge_num]; allop_pt=0; } ~graph(){ delete[] pool; } inline edge* new_edge(int j,int w,edge* next){ pool[allop_pt].j=j; pool[allop_pt].w=w; pool[allop_pt].next=next; return &pool[allop_pt++]; } inline void add_edge(int i,int j,int w){ g[i]=new_edge(j,w,g[i]); g[j]=new_edge(i,w,g[j]); } }; int Q[65536]; void SPFA(graph &g,int source,int dis[]){ int qsize=65535,f=0,b=0; bool in[maxn]={0}; for(int i=0;i<g.node_num;i++)dis[i]=INF; dis[source]=0; Q[f++]=source;f&=qsize; in[source]=true; while(f!=b){ int i=Q[b++];b&=qsize; in[i]=false; for(graph::edge *e=g.g[i];e;e=e->next){ if(dis[e->j] > dis[i]+e->w){ dis[e->j]=dis[i]+e->w; if(!in[e->j]){ Q[f++]=e->j;f&=qsize; in[e->j]=true; } } } } } int main(){ freopen(“in.txt”,”r”,stdin); freopen(“out.txt”,”w”,stdout); int dis[maxn]; int n,m,a,b,c; scanf(“%d%d”,&n,&m); graph G(n,m); while(m–){ scanf(“%d%d%d”,&a,&b,&c); G.add_edge(a,b,c); } SPFA(G,0,dis); for(int i=0;i<n;i++)printf(“%d/n”,dis[i]); }
USACO:
#include <stdio.h> #include <string.h> const int BIG = 1000000000; const int maxn = 100000; int v,e; struct graph{ int node_num,allop_pt; struct edge{ int j,w; edge *next; }*g[maxn],*pool; graph(int node_num,int edge_num):node_num(node_num){ memset(g,0,sizeof(g)); pool=new edge[2*edge_num]; allop_pt=0; } ~graph(){ delete[] pool; } inline edge* new_edge(int j,int w,edge* next){ pool[allop_pt].j=j; pool[allop_pt].w=w; pool[allop_pt].next=next; return &pool[allop_pt++]; } inline void add_edge(int i,int j,int w){ g[i]=new_edge(j,w,g[i]); g[j]=new_edge(i,w,g[j]); } }; int dist[maxn]; int heapsize; int heap_id[maxn]; int heap_val[maxn]; int heap_lookup[maxn]; void heap_swap(int i, int j){ int s; s = heap_val[i]; heap_val[i] = heap_val[j]; heap_val[j] = s; heap_lookup[heap_id[i]] = j; heap_lookup[heap_id[j]] = i; s = heap_id[i]; heap_id[i] = heap_id[j]; heap_id[j] = s; } void heap_up(int i){ if(i > 0 && heap_val[(i-1) / 2] > heap_val[i]){ heap_swap(i, (i-1)/2); heap_up((i-1)/2); } } void heap_down(int i){ int a = 2*i+1; int b = 2*i+2; if(b < heapsize){ if(heap_val[b] < heap_val[a] && heap_val[b] < heap_val[i]){ heap_swap(i, b); heap_down(b); return; } } if(a < heapsize && heap_val[a] < heap_val[i]){ heap_swap(i, a); heap_down(a); } } int main(){ freopen(“in.txt”,”r”,stdin); freopen(“out.txt”,”w”,stdout); scanf(“%d%d”,&v,&e); graph g(v,e); int a,b,c; for(int i = 0; i < e; ++i){ scanf( “%d%d%d”,&a,&b,&c); g.add_edge(a,b,c); } int i=0; heapsize = v; for(int j = 0; j < v; ++j){ heap_id[j] = j; heap_val[j] = BIG; heap_lookup[j] = j; } heap_val[i] = 0; heap_up(i); bool fixed[maxn]; memset(fixed, false, v); for(int j = 0; j < v; ++j){ int p = heap_id[0]; dist[p] = heap_val[0]; fixed[p] = true; heap_swap(0, heapsize-1); –heapsize; heap_down(0); for(graph::edge *e=g.g[p];e;e=e->next){ int q = e->j; if(!fixed[q]){ if(heap_val[heap_lookup[q]] > dist[p] + e->w){ heap_val[heap_lookup[q]] = dist[p] + e->w; heap_up(heap_lookup[q]); } } } } for(int i=0;i<v;i++)printf(“%d/n”,dist[i]); return(0); }