关于求最短路径算法Dijkstra+堆优化的一些粗略讲解(模板)

用途

Dijkstra算法适用于求无负权边的图中点到点的最短路径问题,不可用于有负权边的图中。

算法的基本思想

  1. 选定一个初始点,在后续求得的距离也都是到这点的距离。
  2. 用 dis[] 数组来表示初始点到当前点的最短距离
  3. 遍历当前点连通的所有点,如果当前点满足条件,则更新当前点的 dis[] ,这一步称为 松弛
  4. 同时,将当前点最短距离和点的编号压入小根堆(距离短的在堆上方)
  5. 遍历堆中的元素,因为小根堆的缘故,先遍历的都是距离短的点,这样就可以保证松弛的数据越来越贴近我们的目的
  6. 重复3~5步,直到堆中没有元素为止

dijkstra在用堆优化的同时,我们还可以使用链式前向星存图来进一步优化算法,下面是代码模板

链式前向星

void add(int u,int v,int w) {
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}

Dijkstra

void dijkstra() {
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f3f3f3f,sizeof(dis)); 
    dis[s]=0; //初始点到初始点的最短距离为0
    Q.push(make_pair(0,s)); //将初始点入堆
    while(!Q.empty()) {
        int now=Q.top().second; Q.pop();
        if(vis[now]) continue; //如若当前点已经被访问过,说明当前点的最短距离已经找到,应当直接跳过。这一步的主要作用是用来跳过因为松弛操作加入的多余数据
        vis[now]=1;
        for(int i=head[now];i;i=edge[i].next) { //这一步是遍历链式前向星存的图
            int v=edge[i].v;
            if(dis[now]+edge[i].w<dis[v]) {
                dis[v]=dis[now]+edge[i].w; //更新连通点的最小距离(松弛操作)
                Q.push(make_pair(dis[v],v)); //小根堆
            }
        }
    }
}

最后来看两道例题

题目描述

给定一个 n 个点,m 条有向边的带非负权图,请你计算从 s 出发,到每个点的距离。 数据保证你能从 s 出发到任意点。

输入格式

第一行为三个正整数 n, m, s。 第二行起 m 行,每行三个非负整数 u_i, v_i, w_i,表示从 u_i 到 v_i
有一条权值为 w_i 的有向边。

输出格式

输出一行 n 个空格分隔的非负整数,表示 s 到每个点的距离。

题目来源 https://www.luogu.com.cn/problem/P4779
这道题就是最基本的模板题,用我上面的模板套入就可以了,建议在看了上面的模板后自己写一下,理解一下Dijkstra算法。

AC代码

#include<bits/stdc++.h>
using namespace std;
int n,m,s,dis[100005],vis[100005],head[100005],cnt=1;
priority_queue<pair<int,int> >Q; //默认的大根堆,在后续中灵活操作,起到小根堆作用
struct Edge{
    int v,w,next;
}edge[200005];
void dijkstra() {
    memset(dis,0x3f3f3f3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    Q.push(make_pair(0,s));
    while(!Q.empty()) {
        int now=Q.top().second;Q.pop();
        if(vis[now]) continue;
        vis[now]=1;
        for(int i=head[now];i;i=edge[i].next) {
            int v=edge[i].v;
            if(dis[now]+edge[i].w<dis[v]) {
                dis[v]=dis[now]+edge[i].w; 
                Q.push(make_pair(-dis[v],v)); //这里注意哦,我将Q定义为默认的优先队列,本身是大根堆,但在这里加一个负号就起到了小根堆的作用
        }
    }
}
void add(int u,int v,int w) {
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
int main() {
    cin>>n>>m>>s;
    for(int i=0,u,v,w;i<m;i++) {
        //cin>>u>>v>>w;
        scanf( "%d%d%d", &u, &v, &w );
        add(u,v,w);//单向图,存一次就可以了
    }
    dijkstra();
    for(int i=1;i<=n;i++) {
        //cout<<dis[i]<<" ";
        printf( "%d ", dis[i] );
    }
}

题目描述

给出一个N个顶点M条边的无向无权图,顶点编号为1-N。问从顶点1开始,到其他每个点的最短路有几条。

输入格式

第一行包含2个正整数N,M,为图的顶点数与边数。 接下来M行,每行2个正整数x,y,表示有一条顶点x连向顶点y的边,请注意可能有自环与重边。

输出格式

共N行,每行一个非负整数,第i行输出从顶点1到顶点i有多少条不同的最短路,由于答案有可能会很大,你只需要输出ans mod 100003后的结果即可。如果无法到达顶点i则输出0。

题目来源 https://www.luogu.com.cn/problem/P1144

这个题目中没有明确的边权,求得也不是最短路径的距离。而且顶点到顶点的最短路有多少条,那么我们可以给每条边一个单位边长,例如 1。然后在Dijkstra算法中求最短路时进行判断,如果到目标点有更短的路,则目标点的最短路的条数等于前一个点的的最短路的条数,如果到目标点的距离等于目标点的最短路径距离,则目标点的最短路的条数加一。

AC代码

#include<bits/stdc++.h>
using namespace std;
int cnt=1,head[1000005],vis[1000005],dis[1000005],ans[1000005];
struct Edge {
    int v,next;
}edge[4000005];
priority_queue<pair<int,int> >Q;
void dijkstra() {
    memset(ans,0,sizeof(ans)); //存储点i的最短路数量
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f3f3f3f,sizeof(dis));
    ans[1]=1; //初始化初始点1到点1的最短路为1,即1->1
    dis[1]=0;
    Q.push(make_pair(0,1));
    while(!Q.empty()) {
        int now=Q.top().second; Q.pop();
        if(vis[now]) continue;
        vis[now]=1;
        for(int i=head[now];i;i=edge[i].next) {
            int v=edge[i].v;
            if(now==v) continue;
            if(dis[now]+1<dis[v]) { //若找到更短的路,则更新dis和ans数组,此时的最短路数量应继承前一个点的最短路数量
                dis[v]=dis[now]+1; //设边权为1
                ans[v]=ans[now]%100003;
                Q.push(make_pair(-dis[v],v)); //加负号入堆,大根堆变小根堆
            }
            else if(dis[now]+1==dis[v]) { //如果前一个点到这个点的距离和这个点的最短距离相等,则当前点最短路数量加一
                ans[v]+=ans[now];
                ans[v]%=100003;
            }
        }
    }
}
void add(int u,int v) {
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
int main() {
    int n,m;
    cin>>n>>m;
    for(int i=0,u,v;i<m;i++) {
        cin>>u>>v;
        add(u,v);
        add(v,u); //双向图,存两次
    }
    dijkstra();
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<endl;
}

以上就是我对于dijkstra算法的一些浅显理解和两道例题。

如有错误,欢迎指出。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值