题目描述:
给定一个n个点m条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出-1。
输入格式
第一行包含整数n和m。
接下来m行每行包含三个整数x,y,z,表示点x和点y之间存在一条有向边,边长为z。
输出格式
输出一个整数,表示1号点到n号点的最短距离。
如果路径不存在,则输出-1。
数据范围
1≤n≤500,
1≤m≤10^5,
图中涉及边长均不超过10000。
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
分析:
本题涉及的图为稠密图,采用邻接矩阵存储,无负权边,采用朴素的dijkstra算法时间复杂度为O(n^2)。
简单描述下dijkstra算法的过程:从点集的角度去理解该算法。无负权边,求从1号点到n号点的最短距离。初始点集只包含1号点,我们需要添加一些点直至将n号点也添加进点集为止。
随便弄张图来讲解下算法的流程:初始点集为{A},d[A]=0,其它顶点的初始距离设为不可达INF,A能够到达B和C,则更新下d[B] = 1,d[C] = 5,然后找下除了点集内的点其它点距离的最小值,d[B]最小,为1,所以将B加入点集,点集为{A,B},然后尝试用B来更新相邻节点,更新d[D] = d[B] + 2 = 3,d[E] = d[B] + 1 = 2。再寻找点集中的点可到扩展到的最近的点,发现是E点,遂将E加入点集,更新C点的最短距离为d[C]=4,D点未能更新最短距离,d[G]=4。继续寻找点集{A,B,E}以外可到达的最近的点为D点,加入集合,看看能否用D点更新附近的点,d[F] = 5,继续寻找最近的点为C点,加入点集,现在的点集为{A,B,E,D,C},通过C点没有可更新的距离,将最近的点G加入集合,发现还是不能更新F点,最后将F加入点集,得到{A,B,E,D,C,G,F}。从A到F的最短路径为d[F]=5。
可以发现的是,当一个点被加入点集,起点到该点的最短距离就必然已经确定下来了。初始点集由A扩展到B,显然B是离A最近的点,再扩展到E,说明点集E离集合中的点已经是最近的了,假设点集中点经过某点x再到达E可以更新E的最短距离,由于没有负权边,点集中点到x的距离必然小于到E的距离,而E加入点集的条件恰好是到点集的距离最近,所以不存在这样的点x。以此类推,可以证明当终点被加入到点集中,其最短距离也就确定了。
整理下dijkstra算法的过程:每次遍历下所有的边,找到点集外可加入点集的距离最小的点,加入到点集,再尝试更新下新加入的点附近的点的距离。每次执行下找最短边的操作时间复杂度是O(n),最坏情况下终点在最后才会被加入到点集,所以过程会被重复n次,故朴素的dijkstra算法的时间复杂度为O(n^2)。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 510;
int n,m;
int g[maxn][maxn],d[maxn];
bool vis[maxn];
int dijkstra(){
memset(d,0x3f,sizeof d);//初始距离不可达
d[1] = 0;
for(int i = 0;i < n;i++){//遍历n次
int t = -1;
for(int j = 1;j <= n;j++){
if(!vis[j] && (t == -1 || d[t] > d[j])) t = j;//找点集外初始最短边
}
vis[t] = true;//加入点集
for(int j = 1;j <= n;j++){
if(!vis[j] && d[t] + g[t][j] < d[j]) d[j] = d[t] + g[t][j];//松弛操作
}
}
if(d[n] == 0x3f3f3f3f) return -1;//终点不可达
return d[n];
}
int main(){
scanf("%d%d",&n,&m);
int a,b,c;
memset(g,0x3f,sizeof g);
while(m--){
scanf("%d%d%d",&a,&b,&c);
g[a][b] = min(g[a][b],c);
}
cout<<dijkstra()<<endl;
return 0;
}