1. 程式人生 > >[最短路]使用優先佇列優化的Dijkstra演算法

[最短路]使用優先佇列優化的Dijkstra演算法

用鄰接矩陣的Dijkstra演算法的程式碼:

int cost[RANGE][RANGE];  
int d[RANGE];  
bool used[RANGE];  
int n,m;   //頂點數,邊數
    void Dijkstra( int s )  
    {  
        int i,v,u;  
        for( i=1; i<=n; ++i )  
        {  
            used[i]=false;  
            d[i]=cost[1][i];  
        }  
        d[s]=0;  
    while( true )  
    {  
        v=-1;  
        for( u=1; u<=n; ++u )  
            if( !used[u] && ( v==-1 || d[u]<d[v]) )  
                v=u;  
        if( v==-1 ) break;  
        used[v]=true;  

        for( u=1; u<=n; ++u )  
            d[u]= min( d[u],d[v]+cost[v][u] );  
    }  
}  

使用鄰接矩陣實現的dijkstra演算法的複雜度是O(V²)。使用鄰接表的話,更新最短距離只需要訪問每條邊一次即可,因此這部分的複雜度是O(E).但是每次要列舉所有的頂點來查詢下一個使用的頂點,因此最終複雜度還是O(V²)。在|E|比較小時,大部分的時間都花在了查詢下一個使用的頂點上,因此需要使用合適的資料結構進行優化。

需要優化的是數值的插入(更新)和取出最小值兩個操作,因此使用堆就可以了。把每個頂點當前的最短距離用堆來維護,在更新最短距離時,把對應的元素往根的方向移動以滿足堆的性質。而每次從堆中取出的最小值就是下一次要用的頂點。這樣堆中的元素共有O(V)個,更新和取出的操作有O(E)次,因此整個演算法的複雜度是O(ElogV)。
下面是使用STL的priority_queue實現。在每次更新時往堆裡插入當前最短距離和頂點的值對。插入的次數是O(E)次,當取出的最小值不是最短距離的話,就丟棄這個值。這樣整個演算法也可以在同樣的時間內完成。

struct edge {int to,cost;};
typedef pair<int,int> P; //first是最短距離,second是頂點的編號
int V;//頂點個數
vector<edge> G[MAXV];
int d[MAXV];

void dijkstra(int s)
{
    priority_queue<P,vector<P>,greater<P> > que;
    memset(d,INF,sizeof d);
    d[s] = 0;
    que.push(P(0,s)); //把起點推入佇列
    while
(!que.empty()) { P p = que.top(); que.pop(); int v = p.second; //頂點的編號 if (d[v] < p.first) continue; for(int i = 0; i < G[v].size(); i++) { edge e = G[v][i]; if (d[e.to] > d[v] + e.cost) { d[e.to] = d[v] + e.cost; que.push(P(d[e.to],e.to)); } } } }

相對於Bellman-Ford的O(VE)的複雜度,Dijkstra的複雜度是O(ElogV),可以更加高效地計算最短路的長度。不過需要注意的一點:當圖中存在負邊的情況下,Dijkstra演算法就無法正確求解問題,還是需要使用Bellman-Ford演算法。