1. 程式人生 > >eoj1817 dijkstra單元最短路徑 普通方法+二叉堆優化

eoj1817 dijkstra單元最短路徑 普通方法+二叉堆優化

求出有n(1 < n < 600)個結點有向圖中,結點1到結點n的最短路徑。

Input
第一行有2個整數n和m(0 < m <= n*(n-1)/2),接下來m行每行有三個整數u,v,w結點u到v之間有一條權為w的邊(w<1000000)。

Output
輸出結點1到結點n之間的最短路徑,如果1到n之間不存在路徑,輸出 -1。

Sample Input
3 3
1 2 10
2 3 15
1 3 30

題目分析:dijkstra單元最短路徑。

一.最短路徑的最優子結構性質

該性質描述為:如果P(i,j)={Vi....Vk..Vs...Vj}是從頂點i到j的最短路徑,k和s是這條路徑上的一箇中間頂點,那麼P(k,s)必定是從k到s的最短路徑。下面證明該性質的正確性。

   假設P(i,j)={Vi....Vk..Vs...Vj}是從頂點i到j的最短路徑,則有P(i,j)=P(i,k)+P(k,s)+P(s,j)。而P(k,s)不是從k到s的最短距離,那麼必定存在另一條從k到s的最短路徑P'(k,s),那麼P'(i,j)=P(i,k)+P'(k,s)+P(s,j)<P(i,j)。則與P(i,j)是從i到j的最短路徑相矛盾。因此該性質得證。

二.Dijkstra演算法

   由上述性質可知,如果存在一條從i到j的最短路徑(Vi.....Vk,Vj),Vk是Vj前面的一頂點。那麼(Vi...Vk)也必定是從i到k的最短路徑。為了求出最短路徑,Dijkstra就提出了以最短路徑長度遞增,逐次生成最短路徑的演算法。譬如對於源頂點V0,首先選擇其直接相鄰的頂點中長度最短的頂點Vi,那麼當前已知可得從V0到達Vj頂點的最短距離dist[j]=min{dist[j],dist[i]+matrix[i][j]}。根據這種思路,

假設存在G=<V,E>,源頂點為V0,U={V0},dist[i]記錄V0到i的最短距離,path[i]記錄從V0到i路徑上的i前面的一個頂點。

1.從V-U中選擇使dist[i]值最小的頂點i,將i加入到U中;

2.更新與i直接相鄰頂點的dist值。(dist[j]=min{dist[j],dist[i]+matrix[i][j]})

3.知道U=V,停止。

三,重用鄰接矩陣實現,這對於稀疏圖來說效率往往會很低,常用的優化方法有1,用鄰接表儲存,2用二叉堆優化每次選擇最短路徑的速度。下面分別給出兩種程式碼。

版本一:常用鄰接矩陣實現方法:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <map>
#include <algorithm>

using namespace std;

int cost[605][605],low[605];//cost為鄰接矩陣,low為當前到源點的最短距離
bool vis[605];

const int maxn=1000000;

int main()
{
    int n,m,i,j,k;
    memset(vis,false,sizeof(vis));
    while(~scanf("%d%d",&n,&m)){
        for(i=0;i<605;++i)
            for(j=0;j<605;++j)
                cost[i][j]=maxn;
        for(i=0;i<m;++i){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            cost[a][b]=c;
        }

        for(i=1;i<=n;++i){
            low[i]=cost[1][i];
            vis[i]=false;
        }
        vis[1]=true;
        for(i=2;i<=n;++i){
            int minn=1<<30;
            for(j=1;j<=n;++j){//選擇當前最近點,這裡可用堆優化
                if(!vis[j] && low[j]<minn){
                    minn=low[j];
                    k=j;
                }
            }
            vis[k]=true;//將當前的加入以最優集合,之後不再對這點判斷
            for(j=1;j<=n;++j){//更新
                if(!vis[j] && low[k]+cost[k][j]<low[j]){
                    low[j]=low[k]+cost[k][j];
                }
            }
        }
        if(low[n]>=maxn)
            printf("-1\n");
        else
            printf("%d\n",low[n]);
    }
    return 0;
}

版本二:使用二叉堆和鄰接表,對大資料的稀疏圖效果更好。

#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN=1000000;
vector<int> g[605];//鄰接表,g[i]一次儲存與i鄰接的邊的下標,方便快速查詢
bool vis[605];//標記陣列
int low[605],n,m;//當前最短距離
struct Edge{//邊類
    int from,to,dis;
};
vector<Edge> edge;//儲存邊的情況
struct Node{//用於優先佇列中的節點
    int d,u;
    bool operator<(const Node &b)const{
        return this->d>b.d;
    }
};
void dijkstra(){
    for(int i=0;i<=n;++i) low[i]=MAXN;//初始化
    low[1]=0;
    memset(vis,false,sizeof(vis));
    priority_queue<Node> q;
    q.push((Node){0,1});
    while(!q.empty()){
        Node x=q.top();//快速找當前最短距離點
        q.pop();
        int u=x.u;
        if(vis[u]) continue;//如果該點已經為最優,則忽略
        vis[u]=true;
        for(int i=0;i<g[u].size();++i){//更新
            Edge &e=edge[g[u][i]];
            if(low[e.to]>low[u]+e.dis){
                low[e.to]=low[u]+e.dis;
                q.push((Node){low[e.to],e.to});
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        edge.push_back((Edge){a,b,c});
        g[a].push_back(edge.size()-1);
    }
    dijkstra();
    if(low[n]>=MAXN) printf("-1\n");
    else printf("%d\n",low[n]);
    return 0;
}