求出有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; }