Dijkstra算法+堆優化
Dijkstra算法步驟:
把頂點V分成兩組:
S:已經求出最短路徑的頂點集合
T=V-S:尚未確定最短路徑的頂點集合
1、初始時:令S={V0} T={其余頂點} T中的頂點對應的距離值若存在<V0,Vi>,則為該邊的權值,若不存在則為INF(正無窮)
2、從T中選取一個距離最小的頂點W,將該點加入集合S中。並用該點對T中頂點的距離進行修改:若加入w作為中間頂點(V0-->W-->Vn),該路徑的距離比不加入W的路徑更短,則修改此距離值。
3、重復上述步驟,知道S中包含所有頂點,即S=V為止
分析:
可知Dijkstra算法的復雜度是O(n*n)
Dijkstra算法在尋找集合T中距離最小的頂點W(即尋找dist數組中最小值)復雜度是O(n),在更新距離操作時復雜度為O(n)。
上述操作需要進行n次,將n個點的最短路徑值確定因此復雜度為O(n)。可以發現外層的n次循環是不可避免的,因為需要求出源點到各個點的最短路徑。可以優化的地方就在尋找dist數組最小值。
可以采用合適的數據結構優化該過程,這里采用了小根堆。小根堆查找最小值時復雜度為O(1),更新里面的值時復雜度為O(logn).最后可將Dijkstra復雜度降至O(nlogn).
這里使用C++ STL中的priority_queue實現小根堆的操作,因為priority_queue默認是大根堆,因此需要重載小於運算符,變成小根堆
代碼實現
這里同樣以一道模板題來展示該代碼 (POJ - 2387 Til the Cows Come Home)
//鏈式前向星存圖+迪傑斯特拉堆優化 #include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; const int MAX=1005; const int MAXN=4009; const int INF=0x3f3f3f3f; int head[MAX],cnt=0; int t,n,a,b,len; int dist[MAX]; bool vis[MAX]; struct Edge{ //鏈式前向星 int next,val,to; }Edge[MAXN]; inline void add(int u,int v,int w) { Edge[cnt].to=v; Edge[cnt].val=w; Edge[cnt].next=head[u]; head[u]=cnt++; } struct node { int pos,dist; //點的位置及距離 node(){} node(int p,int d) { pos=p; dist=d; } bool operator < (const node &rhs)const //重載 < { return dist>rhs.dist; } }; void Dij(int start) { priority_queue<node>que; for(int i=1;i<=n;i++) { dist[i]=INF; vis[i]=false; } dist[start]=0; que.push(node(start,0)); while(!que.empty()) { node temp=que.top(); //優先隊列為首的元素及dist數組的最小值 que.pop(); int v=temp.pos; //篩選出最小值 if(vis[v])continue; //判斷是否已經找到最小值 ,是的話跳過 vis[v]=true; for(int i=head[v];i!=-1;i=Edge[i].next) //用最小值的點為弧尾的邊更新距離 { int to=Edge[i].to; if(dist[to]>dist[v]+Edge[i].val) { dist[to]=dist[v]+Edge[i].val; que.push(node(to,dist[to])); } } } } int main() { while(scanf("%d%d",&t,&n)!=EOF) { memset(head,-1,sizeof(head)); for(int i=0;i<t;i++) { scanf("%d%d%d",&a,&b,&len); add(a,b,len); add(b,a,len); } Dij(1); printf("%d\n",dist[n]); } return 0; }
其他存圖方式的Dijkstra算法代碼在上一篇博客中介紹到最短路---Dijkstra,因為大致相仿,僅修改了尋找最小值的方式(優先隊列優化)不一一貼出。
如有錯誤和不足的地方,歡迎指正,謝謝~
