ACwing算法备战蓝桥杯——Day15——Dijkstra

简述:对于一个有编号为1~n的n个结点的有向图(权值为正),对于求点1到点n的最短路径。

初始假设除第一个结点外,所有结点到结点1无路径(也就是每个点都与结点1不连通,可以设边(1,i)的权值为正无穷大来实现);

第二步循环n次(每次确定一个最短路径点),寻找没有确定到结点1最短路径的点的这个集合中,权值最小的为下一个确定为最短路径的点,然后根据这个点的权值,更新未确定最短路径的点权值

最后如果n的权值仍为无穷大,那么说明n与1不连通;

否则输出点n的到1的距离;

模板:

不能存在负权值

int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;     // 在还未确定最短路的点中,寻找距离最小的点
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        // 用t更新其他点的距离
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

模板题:

当数据量很小时,可以用最简单的朴素dijistra

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。

输入格式
第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。

数据范围
1≤n≤500,
1≤m≤105,
图中涉及边长均不超过10000。

输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3

代码及详解:
//n较小,用朴素版dijistra即可
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=510;
int n,m;
int e[N][N],d[N],st[N];//d[N]表示到1的距离,st[N]表示是否确定了到1的最短路

// /时间复杂度为n*n
int dijkstra(){
    //初始化
    memset(d,0x3f,sizeof d);//表示所有点都无法到达1
    d[1]=0;//一到自己的距离为0
    
    //每次寻找当前没确定最短路径过且到d[]最小的点
    //寻找n次,每次确定一个最短路径
    for(int j=0;j<n;j++){
        int t=0;
        for(int i=1;i<=n;i++){
            if(!st[i]&&d[t]>d[i]) t=i;
        }
        st[t]=true;
        for(int i=1;i<=n;i++){//邻接矩阵的边
            if(!e[t][i]) continue;
            d[i]=min(d[i],e[t][i]+d[t]);//被确定入最短路径集合的点距离也会更新
        }
    }
    
    if(d[n]==0x3f3f3f3f) return -1;//memset按字节赋值,一个字节3f,int为四个字节,所以有四个3f
    else return d[n];
}
int main(){
    cin>>n>>m;
    for(int k=0;k<m;k++){//如果用邻接矩阵存边,要特殊处理重边
        int i,j,w;
        scanf("%d%d%d",&i,&j,&w);
        if(!e[i][j]){
            e[i][j]=w;
            continue;
        }
        e[i][j]=min(e[i][j],w);//会有重边,所以只考虑最短的那个
    }
    cout<<dijkstra()<<endl;
    return 0;
}

当数据范围很大时,需要用小根堆优化:

//n较大,要用堆优化
#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int,int> PII;
const int N=150010,M=N;

int n,m;
int h[N],e[M],ne[M],w[M],idex;
int d[N];
int st[N];

void add(int a,int b,int c){
    ne[idex]=h[a],e[idex]=b,w[idex]=c,h[a]=idex++;
}

//时间复杂度n
int dijistra(){
    memset(d,0x3f,sizeof d);
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    d[1]=0;
    heap.push({0,1});
    
    //m*logn
    while(heap.size()){
        PII temp=heap.top();
        heap.pop();
        
        int t=temp.second,dist=temp.first;
        if(st[t]) continue;
        st[t]=true;
        
        for(int j=h[t];j!=-1;j=ne[j]){//每条边都加入堆,堆里最多m个元素
            d[e[j]]=min(d[e[j]],d[t]+w[j]);
            heap.push({d[e[j]],e[j]});//堆里插入数据时logn级别
        }
    }
    return (d[n]==0x3f3f3f3f)?-1:d[n];
}


int main(){
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    printf("%d\n",dijistra());
    return 0;
}

好题:最短距离

关键:增设一个源点

有 N 个村庄,编号 1 到 N。

村庄之间有 M 条无向道路,第 i 条道路连接村庄 ai 和村庄 bi,长度是 ci。

所有村庄都是连通的。

共有 K 个村庄有商店,第 j 个有商店的村庄编号是 xj。

然后给出 Q 个询问,第 k 个询问给出一个村庄的编号 yk,问该村庄距离最近的商店有多远?

输入格式
第一行包含两个整数 N,M。

接下来 M 行,每行包含三个整数 ai,bi,ci,表示第 i 条道路连接村庄 ai 和村庄 bi,长度是 ci。

再一行包含整数 K。

接下来 K 行,每行包含一个整数 xj,表示第 j 个有商店的村庄编号是 xj。

再一行包含整数 Q。

接下来 Q 行,每行包含一个整数 yk,表示询问编号为 yk 的村庄与其距离最近的商店之间的距离。

输出格式
对于每个询问,输出该询问的结果。

数据范围
2≤N≤105,
N−1≤M≤min(N(N−1)2,105),
1≤Q≤105,
1≤K≤N,
1≤ci≤10000
输入样例:
7 7
1 2 5
1 4 3
2 3 2
2 5 1
3 6 7
5 6 8
6 7 6
3
7
5
4
7
1
2
3
4
5
6
7
输出样例:
3
1
3
0
0
6
0

代码:
//用堆优化dijistra也会超时
//可以设置一个源点有k条边指向k个超市,可以优化成只用一次dijistra算法
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
typedef pair<int,int> PII;
const int N=1e5+10,M=3*N;

//d[]不会爆int
int h[N],ne[M],e[M],idex;//邻接表
int n,m;
int w[M];//权值数组
int d[N];//到某点的距离
bool st[N];//是否确定最短路

void add(int a,int b,int c){
    ne[idex]=h[a],e[idex]=b,w[idex]=c,h[a]=idex++;
}

void dijistra(){
    memset(d,0x3f,sizeof d);
    d[0]=0;
    
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0,0});

    while(heap.size()){
        PII temp=heap.top();
        heap.pop();
        
        int t=temp.second,dist=temp.first;
        if(st[t]) continue;
        st[t]=true;
        
        for(int j=h[t];j!=-1;j=ne[j]){
            d[e[j]]=min(d[e[j]],d[t]+w[j]);
            heap.push({d[e[j]],e[j]});
        }
    }
}

int main(){
    cin>>n>>m;
    
    memset(h,-1,sizeof h);
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    
    int k;
    cin>>k;
    while(k--){
        int s;
        scanf("%d",&s);
        add(0,s,0);//添加超市与源点的边
    }
    
    dijistra();
    int Q;
    cin>>Q;
    while(Q--){
        int q;
        scanf("%d",&q);
        printf("%d\n",d[q]);
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

切勿踌躇不前

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值