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