题目描述:
给定一个n个点m条边的有向图,图中可能存在重边和自环,所有边权均为正值。
请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出-1。
输入格式
第一行包含整数n和m。
接下来m行每行包含三个整数x,y,z,表示点x和点y之间存在一条有向边,边长为z。
输出格式
输出一个整数,表示1号点到n号点的最短距离。
如果路径不存在,则输出-1。
数据范围
1≤n,m≤10^5,
图中涉及边长均不超过10000。
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
分析:
稀疏图,采用邻接表存储,用堆优化版dijkstra算法解决,时间复杂度为O(mlogn)。
朴素版的dijkstra时间消耗在每次用O(n)的时间去寻找最短边了,但是由于稠密图点数不多并没有太大影响,对于稀疏图而言,可以采用优先级队列优化。开始将1号节点入队,之后分别将更新了最短距离的点入队,最短距离的点只会在距离被更新过的点里,所以每次取堆顶元素加入点集即可。这里需要注意:新加入队列的点可能之前已经加入队列了,比如1号节点更新了2,3号节点的距离为1,3,然后2号节点将3号节点的距离更新为2,再次将3号节点入队,队列里便有了两个3号节点了,但由于每次取的是堆顶元素,只会是距离最小的边,并没有影响,为了防止同一节点多次出队再遍历边浪费时间,加个判断已经加入点集的节点再次出队不会再遍历边即可。
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
const int maxn = 100005;
typedef pair<int,int> pii;
int n,m,idx;
int d[maxn],h[maxn],e[maxn],ne[maxn],w[maxn];
bool vis[maxn];
priority_queue<pii,vector<pii>,greater<pii> > q;
void add(int a,int b,int c){
e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;//头插法,ne,h数组里存储的均是边的编号
}
int dijkstra(){
memset(d,0x3f,sizeof d);
d[1] = 0;
q.push({0,1});//编号为1的顶点,距离为0
while(q.size()){
auto u = q.top();
int dis = u.first,v = u.second;
q.pop();
if(vis[v]) continue;//已经加入点集的点不再更新距离
vis[v] = true;//加入点集标志
for(int i = h[v];i != -1;i = ne[i]){
int j = e[i];
if(!vis[j] && d[j] > dis + w[i]){//松弛操作
d[j] = dis + w[i];
q.push({d[j],j});
}
}
}
if(d[n] == 0x3f3f3f3f) return -1;
return d[n];
}
int main(){
scanf("%d%d",&n,&m);
int a,b,c;
memset(h,-1,sizeof h);
while(m--){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
cout<<dijkstra()<<endl;
return 0;
}
时间复杂度分析:每次取出堆顶的点都会遍历下其连接的边,比如第一次要遍历的边数为m1,向堆里插入更新距离了的点的复杂度为O(logn),所以总的时间复杂度为m1logn + m2logn + ....。由于所有边数总和为m,所以总的时间复杂度为O(mlogn)。
由于STL的优先级队列效率较低,且没办法取修改堆里面元素,下面再用手写可修改的堆来优化dijkstra算法。
手写堆模板见AcWing 839 模拟堆。
在上面的代码里,堆里存储着节点的编号和距离,而手写堆的时候,堆he只需存储到节点的距离,初始情况下距离均为INF。ph[i]表示第i个插入的元素在堆的哪个位置,hp[i]表示堆里第i个元素是第几次插入的。初始情况ph[i]=hp[i]=i,通过hp数组即可得到堆里元素插入的时机,也就是节点的编号。具体的操作见代码:
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int maxn = 100005;
typedef pair<int,int> pii;
int n,m,idx;
int he[maxn], ph[maxn], hp[maxn], size;
int d[maxn],h[maxn],e[maxn],ne[maxn],w[maxn];
bool vis[maxn];
void add(int a,int b,int c){
e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;//头插法,ne,h数组里存储的均是边的编号
}
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(he[a], he[b]);
}
void down(int u)
{
int t = u;
if (u * 2 <= size && he[u * 2] < he[t]) t = u * 2;
if (u * 2 + 1 <= size && he[u * 2 + 1] < he[t]) t = u * 2 + 1;
if (u != t)
{
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
while (u / 2 && he[u] < he[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
int dijkstra(){
memset(d,0x3f,sizeof d);
memset(he,0x3f,sizeof he);
d[1] = 0,he[1] = 0,size = n;
for(int i = 1;i <= n;i++) hp[i] = ph[i] = i;
while(size){
int dis = he[1],v = hp[1];//hp[1]表示堆顶节点的序号
heap_swap(1,size--);
down(1);
vis[v] = true;
for(int i = h[v];i != -1;i = ne[i]){
int j = e[i];
if(!vis[j] && d[j] > dis + w[i]){
d[j] = dis + w[i];
he[ph[j]] = d[j];//ph[j]表示编号为j的节点在堆里的位置
up(ph[j]),down(ph[j]);
}
}
}
if(d[n] == 0x3f3f3f3f) return -1;
return d[n];
}
int main(){
scanf("%d%d",&n,&m);
int a,b,c;
memset(h,-1,sizeof h);
while(m--){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
cout<<dijkstra()<<endl;
return 0;
}