AcWing 850 Dijkstra求最短路 II

题目描述:

给定一个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;
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值