<蓝桥杯软件赛>零基础备赛20周--第19周--最短路

news2024/11/16 13:48:05

报名明年4月蓝桥杯软件赛的同学们,如果你是大一零基础,目前懵懂中,不知该怎么办,可以看看本博客系列:备赛20周合集
20周的完整安排请点击:20周计划
每周发1个博客,共20周。
在QQ群上交流答疑:

在这里插入图片描述

文章目录

  • 1. Floyd算法
  • 2. Bellman-ford算法
  • 3. Dijkstra算法
  • 4. 练习题

第19周:最短路

  最短路问题是最广为人知的图论问题,也是蓝桥考核最多的图论问题。
  在“第十四周 BFS”中提到BFS也是一种很不错的最短路算法。不过它只适合一种场景:任意的相邻两点之间距离相等,一般把这个距离看成1,称为“1跳”,从起点到终点的路径长度就是多少个“跳数”。在这种场景下,查找一个起点到一个终点的最短距离,BFS是最优的最短路径算法,计算复杂度是O(n),n是图上点的数量。
  在更多的应用场景中,需要用不同的算法来解决, 有这些通用的最短路径算法:Floyd、Dijkstra、Bellman-ford、SPFA算法。
  Floyd算法是最简单的最短路径算法,代码仅有4行且非常易懂。它的效率不高,不能用于大图,但是在某些场景下也有自己的优势,难以替代。Floyd算法是一种“多源”最短路算法,一次计算能得到图中每一对结点之间(多对多)的最短路径。
  Bellman-Ford、Dijkstra、SPFA算法都是“单源”最短路径算法,一次计算能得到一个起点到其他所有点(一对多)的最短路径。
  蓝桥杯以前经常考最短路,考过Floyd、BFS、Bellman-Ford、Dijkstra,不过这2年没怎么出题,是不是因为以前考多了,累了。例如2023年省赛的几十道题中只考了一题最短路,用到Dijkstra。
  下面介绍Floyd、Bellman-Ford、Dijkstra。Floyd、Bellman-Ford都非常简单,代码短,存图的数据结构简单。Dijkstra难一些,初学者可能比较费劲。

1. Floyd算法

  求图上两点i、j之间的最短距离,可以按“从小图到全图”的步骤,在逐步扩大图的过程中计算和更新最短路,这是动态规划的思路。定义状态为dp[k][i][j],i、j、k是点的编号,范围1 ~ n。状态dp[k][i][j]表示在包含1 ~ k点的子图上,点对i、j之间的最短路。当从子图1 ~ k-1扩展到子图1 ~ k时,状态转移方程这样设计:
    dp[k][i][j] = min(dp[k-1][i][j], dp[k-1][i][k] + dp[k-1][k][j])
  计算过程如下图所示,虚线圆圈内是包含了1 ~ k-1点的子图。方程中的dp[k-1][i][j]是虚线子图内的点对i、j的最短路;dp[k-1][i][k] + dp[k-1][k][j]是经过k点的新路径的长度,即这条路径从i出发,先到k,再从k到终点j。比较不经过k的最短路径dp[k-1][i][j]和经过k的新路径,较小者就是新的dp[k][i][j]。每次扩展一个新点k时,都能用到1 ~ k-1的结果,从而提高了效率。这就是动态规划的方法。
在这里插入图片描述
      图1 从子图1~k-1扩展到1~k

  当k从1逐步扩展到n时,最后得到的dp[n][i][j]是点对i、j之间的最短路径长度。由于i和j是图中所有的点对,所以能得到所有点对之间的最短路。
  初值dp[0][i][j],若i、j是直连的,就是它们的边长;若不直连,赋值为无穷大。
  由于i、j是任意点对,所以计算结束后得到了所有点对之间的最短路。
  下面是代码,仅有4行。这里把dp[][][]缩小成了dp[][],用到了滚动数组,因为dp[k][][]只和dp[k-1][][]有关,所以可以省掉k这一维。由于k是动态规划的子问题的“阶段”,即k是从点1开始逐步扩大到n的,所以k循环必须放在i、j循环的外面。三重循环,复杂度 O ( n 3 ) O(n^3) O(n3)

for(int k=1; k<=n; k++)         //floyd的三重循环
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)      // k循环在i、j循环外面
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]); //比较:不经过k、经过k

  Floyd算法的寻路极为盲目,几乎“毫无章法”,这是它的效率低于其他算法的原因。但是,这种“毫无章法”,在某些情况下却有优势。
  与其他最短路径算法相比,Floyd有以下特点。
  (1)能在一次计算后求得所有结点之间的最短距离,其他最短路径算法都做不到。
  (2)代码极其简单,是最简单的最短路算法。三重循环结束后,所有点对之间的最短路都得到了。
  (3)效率低下,计算复杂度是 O ( n 3 ) O(n^3) O(n3),只能用于n < 300的小规模的图。
  (4)存图用邻接矩阵dp[][]是最好最合理的,不用更省空间的邻接表。因为Floyd算法计算的结果是所有点对之间的最短路,本身就需要 n 2 n^2 n2的空间,用矩阵存储最合适。
  (5)能判断负圈。负圈是什么?若图中有权值为负的边,某个经过这个负边的环路,所有边长相加的总长度也是负数,这就是负圈。在这个负圈上每绕一圈,总长度就更小,从而陷入在负圈上兜圈子的死循环。Floyd算法很容易判断负圈,只要在算法运行过程出现任意一个dp[i][i] < 0就说明有负圈。因为dp[i][i]是从i出发,经过其他中转点绕一圈回到自己的最短路径,如果小于零,就存在负圈。
  下面的场景适用Floyd算法。
  (1)图的规模小,点数n < 400。计算复杂度 O ( n 3 ) O(n^3) O(n3)限制了图的规模。这种小图不需要用其他算法,其他算法的代码长,写起来麻烦。
  (2)问题的解决和中转点有关。这是Floyd算法的核心思想,算法用DP方法遍历中转点来计算最短路。
  (3)路径在“兜圈子”,一个点可能多次经过。这是Floyd算法的特长,其他路径算法都不行。
  (4)允许多次询问不同点对之间的最短路。这是Floyd算法的优势。


蓝桥公园
【题目描述】小明来到了蓝桥公园。已知公园有N个景点,景点和景点之间一共有M条道路。小明有Q个观景计划,每个计划包含一个起点st 和一个终点ed,表示他想从st去到ed。但是小明的体力有限,对于每个计划他想走最少的路完成,你可以帮帮他吗?
【输入描述】输入第一行包含三个正整数 N,M,Q。第2到M+1行每行包含三个正整数u,v,w,表示 u、v之间存在一条距离为w的路。第M+2到M+Q−1行每行包含两个正整数st,ed,其含义如题所述。
1≤N≤400,1≤M≤N×(N−1)/2,Q≤103,1≤u,v,st,ed≤n,1≤w≤109
【输出描述】输出共Q行,对应输入数据中的查询。若无法从st到达ed则输出−1。


  这一题简单演示了Floyd算法的基本应用。边数1 ≤ M ≤ N×(N−1)/2,说明是一个稠密图。当M = N×(N−1)/2时,任意两个点之间都有边。
  代码很简单,但是也有一些坑点,请仔细看注释。
c++代码

#include <bits/stdc++.h>
using namespace std;
const long long INF = 0x3f3f3f3f3f3f3f3fLL;  //这样定义INF的好处是: INF <= INF+x
const int N = 405;
long long dp[N][N];
int n,m,q;
void input(){
   // for(int i = 1; i <= n; i++)
   //     for(int j = 1; j <= n; j++)   dp[i][j] = INF;
    memset(dp,0x3f,sizeof(dp));                //初始化,和上面2行功能一样
    for(int i = 1; i <= m; i++){
        int u,v;long long w;
        cin >> u >> v >> w;
        dp[u][v]=dp[v][u] = min(dp[u][v] , w);   //防止有重边
    }
}
void floyd(){
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k][j]);
}
void output(){    
    while(q--){
        int s, t; cin >> s >>t;
        if(dp[s][t]==INF) cout << "-1" <<endl;
        else if(s==t) cout << "0" <<endl;      //如果不这样,dp[i][i]不等于0
        else          cout <<dp[s][t]<<endl;
    }
}
int main(){
    cin >> n>> m >> q;
    input();     floyd();    output();
    return 0;
}

java代码

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static final long INF = 0x3f3f3f3f3f3f3f3fL;
    static final int N = 405;
    static long[][] dp = new long[N][N];
    static int n, m, q;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        q = sc.nextInt();
        input(sc);
        floyd();
        output(sc);
    }

    static void input(Scanner sc) {
        for (int i = 1; i <= n; i++)  Arrays.fill(dp[i], INF);        
        for (int i = 1; i <= m; i++) {
            int u = sc.nextInt();
            int v = sc.nextInt();
            long w = sc.nextLong();
            dp[u][v] = dp[v][u] = Math.min(dp[u][v], w);
        }
    }

    static void floyd() {
        for (int k = 1; k <= n; k++) 
            for (int i = 1; i <= n; i++) 
                for (int j = 1; j <= n; j++) 
                    dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k][j]);
    }
    static void output(Scanner scanner) {
        while (q-- > 0) {
            int s = scanner.nextInt();
            int t = scanner.nextInt();
            if (dp[s][t] == INF)   System.out.println("-1");
            else if (s == t)       System.out.println("0");
            else                   System.out.println(dp[s][t]);            
        }
    }
}

Python代码

n,m,q=map(int,input().split())
dp = [[int(0x3f3f3f3f3f3f3f3f) for _ in range(405)]for _ in range(405)]
def input_():
    global dp
    for i in range(1,m+1):
        u,v,w = map(int,input().split())
        ww = min(dp[u][v],w)  #去掉重边
        dp[u][v] = ww
        dp[v][u] = ww
def floyd():
    global dp
    for k in range(1, n + 1):
        for i in range(1, n + 1):
            for j in range(1, n + 1):
                if dp[i][k]+dp[k][j]<dp[i][j]:  dp[i][j] = dp[i][k]+dp[k][j]
def output():
    global q
    for i in range(q):
        s,t = map(int,input().split())
        if dp[s][t] == 0x3f3f3f3f3f3f3f3f:  print(-1)
        elif s==t:                          print(0)
        else:                               print(dp[s][t])
input_()
floyd()
output()

2. Bellman-ford算法

  Bellman-ford算法是单源最短路径算法,它的原理十分简单易懂:一个有n个点的图,给每个点n次机会询问邻居,是否有到起点s的更短的路径,如果有就更新;经过n轮更新,就得到了所有点到起点s的最短路。
  第1轮:起点s的邻居点中,肯定有一个点u是最近的;第1轮确定了s到u的最短路。
  第2轮:所有点再次询问邻居,是否有到s的更短的路;显然,要么是s的某个邻居,要么是u的某个邻居,能确定最短路。
  重复以上步骤,每一轮能确定一个点的最短路。n个点共n轮计算,每一轮需要检查所有的m条边,总复杂度O(m×n)。Bellman-ford算法能用于边权为负数的图,这是它对Dijkstra算法的优势,基于“扩散+贪心”的Dijkstra算法,边的权值不能为负。
  Bellman-Ford算法的特点是只对相邻结点进行计算,可以避免floyd那种大撒网式的无效计算,大大提高了效率。为理解这个算法,可以想象图上的每个点都站着一个人,初始时,所有人到s的距离设为INF,即无限大。用下面的步骤求最短路径:
  (1)第一轮,给所有的n个人每人一次机会,问他的邻居,到s的最短距离是多少?如果他的邻居到s的距离不是INF,他就能借道这个邻居到s去,并且把自己原来的INF更新为较短的距离。显然,开始的时候,起点s的直连邻居(例如u)肯定能更新距离,而u的邻居(例如v),如果在u更新之后问u,那么v有机会更新,否则就只能保持INF不变。特别地,在第一轮更新中,存在一个与s最近的邻居t;t到s的直连距离,就是全图中t到s的最短距离。因为它通过别的邻居绕路到s,肯定更远。t的最短距离已经得到,后面不会再更新。
  (2)第二轮,重复第一轮的操作,再给每个人一次问邻居的机会。这一轮操作之后,至少存在一个s或t的邻居v,可以算出它到s的最短距离。v要么与s直连,要么是通过t到达s的。v的最短距离也得到了,后面不会再更新。
  (3)第三轮,再给每个人一次机会…
  继续以上操作,直到所有人都不能再更新最短距离为止。
  一共需要几轮操作?每一轮操作,都至少有一个新的结点得到了到s的最短路径。所以,最多只需要n轮操作,就能完成n个结点。在每一轮操作中,需要检查所有m个边,更新最短距离。根据以上分析,Bellman-Ford算法的复杂度是O(nm)。
  Bellman-Ford有现实的模型,即问路。每个十字路口站着一个警察;在某个路口,路人问一个警察,怎么走到s最近?如果这个警察不知道,他会问相邻几个路口的警察:“从你这个路口走,能到s吗?有多远?”这些警察可能也不知道,他们会继续问新的邻居。这样传递下去,最后肯定有个警察是s路口的警察,他会把s的信息返回给他的邻居,邻居再返回给邻居。最后所有的警察都知道怎么走到s。而且是最短的路。
  问路模型里有趣的一点,并且能体现Bellman-Ford思想的是:警察并不需要知道到s的完整的路径,他只需要知道从自己的路口出发,往哪个方向走能到达s,并且路最近。


2022第十三届国赛 出差
【题目描述】A国有N个城市,编号为1 . . . N。小明是编号为1的城市中一家公司的员工,今天突然接到了上级通知需要去编号为N的城市出差。由于疫情原因,很多直达的交通方式暂时关闭,小明无法乘坐飞机直接从城市1到达城市N,需要通过其他城市进行陆路交通中转。小明通过交通信息网,查询到了M条城市之间仍然还开通的路线信息以及每一条路线需要花费的时间。同样由于疫情原因,小明到达一个城市后需要隔离观察一段时间才能离开该城市前往其他城市。通过网络,小明也查询到了各个城市的隔离信息。(由于小明之前在城市1,因此可以直接离开城市1,不需要隔离)由于上级要求,小明希望能够尽快赶到城市N,因此他求助于你,希望你能帮他规划一条路线,能够在最短时间内到达城市N。
【输入】第1行:两个正整数N, M, N表示A国的城市数量,M表示未关闭的路线数量。第2行:N个正整数,第i个整数Ci表示到达编号为i的城市后需要隔离的时间。第3 . . . M + 2行:每行3个正整数,u, v, c,表示有一条城市u到城市v的双向路线仍然开通着,通过该路线的时间为c。
【输出】第1行:1个正整数,表示小明从城市1出发到达城市N的最短时间(到达城市N,不需要计算城市N的隔离时间)。
对于100%的数据,1≤N≤1000 , 1≤M≤10000, 1≤Ci≤200, 1≤u, v≤N, 1≤c≤1000


  本题是求最短路径,数据规模1≤N≤1000 , 1≤M≤10000不算大。用那种算法?用复杂度 O ( n 3 ) O(n^3) O(n3)的floyd算法超时;用复杂度O(mn)的Bellman-ford算法正好;没有必要使用更好的Dijkstra和SPFA算法。
两点之间的边长,除了路线时间c,还要加上隔离时间。经过这个转化后,本题是一道简单的Bellman-ford算法模板题。
(1)C++代码
  第7行用struct edge{int a,b,c;}来存边。这种简单的存储方法,不能快速搜索点和边,不过正适合Bellman-ford这种简单的算法。本题的边是无向边,要存为双向边,见13-14行。
  Bellman-ford算法的代码相当简单,几乎和Floyd的代码一样短。

#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int M = 20010;            //双向边的最大数量
int t[M];
int dist[M];                    //dist[i]: 起点到第i点的最短路
struct edge{int a,b,c;}e[M];    //分开操作也行:int a[M],b[M],c[M];
int main(){
    int n,m; cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>t[i];
    for(int i=1;i<=m;i++){
        int a,b,c;  cin >>a>>b>>c;
        e[i].a=a;   e[i].b=b;   e[i].c=c;    //双向边,a->b
        e[m+i].a=b; e[m+i].b=a; e[m+i].c=c;  //双向边: b->a
    }
    //下面是bellman-ford
    memset(dist,INF,sizeof(dist));  //初始化为无穷大
    dist[1]=0;                      //起点是1,1到自己距离为0
    for(int k=1;k<=n;k++){          //一共有n轮操作
        for(int i=1;i<=2*m;i++){    //检查每条边
            int u=e[i].a,v=e[i].b;  //u的一个邻居是v
            int res = t[v];         //隔离时间
            if(v==n) res = 0;       //终点不用隔离
            dist[v]=min(dist[v],dist[u]+e[i].c+res);  //u通过v到起点的距离更短,更新
        }
    }
    cout<<dist[n];
    return 0;
}

java代码

import java.util.Arrays;
import java.util.Scanner;
public class Main {
    static final int INF = 0x3f3f3f3f;
    static final int M = 20010;
    static int[] t = new int[M];
    static int[] dist = new int[M];
    static Edge[] e = new Edge[M];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        for (int i = 1; i <= n; i++)  t[i] = sc.nextInt();        
        for (int i = 1; i <= m; i++) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            int c = sc.nextInt();
            e[i] = new Edge(a, b, c);
            e[m + i] = new Edge(b, a, c);
        }
        bellmanFord(n, m);
        System.out.println(dist[n]);
    }

    static void bellmanFord(int n, int m) {
        Arrays.fill(dist, INF);
        dist[1] = 0;
        for (int k = 1; k <= n; k++) {
            for (int i = 1; i <= 2 * m; i++) {
                int u = e[i].a;
                int v = e[i].b;
                int res = t[v];
                if (v == n)   res = 0;                
                dist[v] = Math.min(dist[v], dist[u] + e[i].c + res);
            }
        }
    }

    static class Edge {
        int a;
        int b;
        int c;
        public Edge(int a, int b, int c) {
            this.a = a;  this.b = b;  this.c = c;
        }
    }
}

python代码
  下面的代码原样改写了上面的C++代码。也用简单的数组存边。由于Python很慢,这份代码的复杂度虽然和上面C++代码一样,但是运行时间特别长,判题系统需要加到10倍时间。

n, m = map(int, input().split())
t= [0]+[int(i) for i in input().split()]     #不用t[0],从t[1]开始
e = []                       #简单的数组存点和边
for i in range(1,m+1):
    a, b,c = map(int, input().split())
    e.append([a,b,c]) 
    e.append([b,a,c])        #双向边
dist=[0x3f3f3f3f]*(n+1)
dist[1]=0
for k in range(1,n+1):
    for a,b,c in e:          #检查每条边
        res=t[b]
        if b==n: res=0
        dist[b]=min(dist[b],dist[a]+c+res)
print(dist[n])

3. Dijkstra算法

  Dijkstra算法是最有名的最短路径算法,也是一般性的最短路径问题中最常用、效率最高的。与Floyd这种“多源”最短路径算法不同,Dijkstra是一种“单源”最短路径算法,一次计算能得到从一个起点s到其他所有点的最短距离长度,并能记录到每个点的最短路径上的途径点。
1. 算法思想
  Dijkstra的模型用多米诺骨牌做例子,读者可以想象下面的场景。
  在图中所有的边上,排满多米诺骨牌,相当于把骨牌看成图的边。一条边上的多米诺骨牌数量,和边的权值(例如长度或费用)成正比。规定所有骨牌倒下的速度都是一样的。如果在一个结点上推倒骨牌,会导致这个结点上的所有骨牌都往后面倒下去。
  在起点s推倒骨牌,可以观察到,从s开始,它连接的边上的骨牌都逐渐倒下,并到达所有能达到的结点。在某个结点t,可能先后从不同的线路倒骨牌过来;先倒过来的骨牌,其经过的路径,肯定就是从s到达t的最短路;后倒过来的骨牌,对确定结点t的最短路没有贡献,不用管它。
  从整体看,这就是一个从起点s扩散到整个图的过程。
  在这个过程中,观察所有结点的最短路径是这样得到的:
  (1)在s的所有直连邻居中,最近的邻居u,骨牌首先到达。u是第一个确定最短路径的结点。从u直连到s的路径肯定是最短的,因为如果u绕道别的结点到s,必然更远。
  (2)然后,把后面骨牌的倒下分成2部分,一部分是从s继续倒下到s的其它的直连邻居,另一部分从u出发倒下到u的直连邻居。那么下一个到达的结点v,必然是s或者u的一个直连邻居。v是第二个确定最短路径的结点。
  (3)继续以上步骤,在每一次迭代过程中,都能确定一个结点的最短路径。
  Dijkstra算法应用了贪心法的思想,即“抄近路走,肯定能找到最短路径”。算法可以简单概况为:Dijkstra = BFS + 贪心。实际上,“Dijkstra + 优先队列 = BFS + 优先队列(队列中的数据是从起点到当前点的距离)”。
  下面分析复杂度。设图的点有n个,边有m条。编码的时候,集合A一般用优先队列来模拟。优先队列可以用堆或其他高效的数据结构实现,往优先队列中插入一个数、取出最小值的操作都是O(logn)的。一共往队列中插入m次(每条边都要进集合A一次),取出n次(每次从集合A中取出距离s最短的一个点,取出时要更新这个点的所有邻居到s的距离,设一个点平均有k个邻居),那么总复杂度是O(m×logn + n×k×logn) O(m×logn),一般有m大于n。
2. 编程实现
  代码的主要内容是维护两个集合:已确定最短路径的结点集合A、这些结点向外扩散的邻居结点集合B。步骤是:
  (1)把起点s放到A中,把s所有的邻居放到B中。此时,邻居到s的距离就是直连距离。
  (2)从B中找出距离起点s最短的结点u,放到A中。
  (3)把u所有的新邻居放到B中。显然,u的每一条边都连接了一个邻居,每个新邻居都要加进去。其中u的一个新邻居v,它到s的距离dis(s, v)等于dis(s, u) + dis(u, v)。
  (4)重复(2)、(3),直到B为空时,结束。
  计算结束后,可以得到从起点s到其它所有点的最短距离。
  用下面的例题给出模板代码,其中有2个关键。
  用邻接表存图和查找邻居。对邻居的查找和扩展,通过动态数组vector e[N]实现的邻接表。其中e[i]存储第i个结点上所有的边,边的一头是它的邻居,即struct edge的参数to。需要扩展结点i的邻居的时候,查找e[i]即可。已经放到集合A中的结点,不要扩展;用bool done[N]记录集合A,当done[i] = true时,表示它在集合A中,已经找到了最短路。
  在集合B中找距离起点最短的结点。直接用STL的优先队列实现,priority_queue <s_node> Q。但是有关丢弃的动作,STL的优先队列无法做到。例如步骤(3)中,需要在B={(2-5), (2-4), (4,7)}中丢弃(2-5),但是STL没有这种操作。在代码中也用bool done[NUM]协助解决这个问题。从优先队列pop出(2-4)时,记录done[2] = true,表示结点2已经处理好。下次从优先队列pop出(2-5)时,判断done[2]是true,丢弃。


蓝桥王国
【题目描述】蓝桥王国一共有N个建筑和M条单向道路,每条道路都连接着两个建筑,每个建筑都有自己编号,分别为1∼。(其中皇宫的编号为1)国王想让小明回答从皇宫到每个建筑的最短路径是多少,但紧张的小明此时已经无法思考,请你编写程序帮助小明回答国王的考核。
【输入描述】输入第一行包含2个正整数N,M。第2到M+1行每行包含三个正整数u,v,w,表示u→v之间存在一条距离为w的路。1≤N≤3×105,1≤m≤106,1≤ui ,vi≤N,0≤wi≤109。
【输出描述】输出仅一行,共N个数,分别表示从皇宫到编号为1∼N建筑的最短距离,两两之间用空格隔开。(如果无法到达则输出−1)


(1)C++代码
  下面是基本Dijkstra算法的代码,使用了“邻接表 + 优先队列”。代码的详细内容已经在前面解释。

#include<bits/stdc++.h>
using namespace std;
const long long INF = 0x3f3f3f3f3f3f3f3fLL; //这样定义INF的好处是: INF <= INF+x
const int N= 3e5+2;
struct edge{
    int from, to; long long w; //起点,终点,权值。起点from并没有用到,e[i]的i就是from
    edge(int a, int b,long long c){from=a; to=b; w=c;}
};
vector<edge>e[N];  		   //用于存储图
struct s_node{
    int id; long long n_dis;   //id:结点;n_dis:这个结点到起点的距离
    s_node(int b,long long c){id=b; n_dis=c;}
    bool operator < (const s_node & a) const
    { return n_dis > a.n_dis;}
};
int n,m;

long long  dis[N];         //记录所有结点到起点的距离
void dijkstra(){
    int s = 1;             //起点s是1
    bool done[N]; //done[i]=true表示到结点i的最短路径已经找到
    for (int i=1;i<=n;i++) {dis[i]=INF; done[i]=false; }    //初始化
    dis[s]=0;                           //起点到自己的距离是0
    priority_queue <s_node> Q;          //优先队列,存结点信息
    Q.push(s_node(s, dis[s]));          //起点进队列
    while (!Q.empty())   {
        s_node u = Q.top();             //pop出距起点s距离最小的结点u
        Q.pop();
        if(done[u.id])  continue;       //丢弃已经找到最短路径的结点。即集合A中的结点
        done[u.id]= true;
        for (int i=0; i<e[u.id].size(); i++) {  //检查结点u的所有邻居
            edge y = e[u.id][i];         //u.id的第i个邻居是y.to
            if(done[y.to])  continue;    //丢弃已经找到最短路径的邻居结点
            if (dis[y.to] > y.w + u.n_dis) {
                dis[y.to] = y.w + u.n_dis;
                Q.push(s_node(y.to, dis[y.to]));  //扩展新的邻居,放到优先队列中
            }
        }
    }

}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)    e[i].clear();
    while (m--) {
        int u,v,w;  scanf("%d%d%lld",&u,&v,&w);
        e[u].push_back(edge(u,v,w));
     // e[v].push_back(edge(v,u,w));    //本题是单向道路
    }
    dijkstra();
    for(int i=1;i<=n;i++){
        if(dis[i]>=INF)  cout<<"-1 ";
        else   printf("%lld ", dis[i]);
    }
}

java代码

import java.util.*;
public class Main {
    static final long INF = 0x3f3f3f3f3f3f3f3fL;
    static final int N = 300002;
    static class Edge {
        int from;
        int to;
        long w;
        public Edge(int from, int to, long w) {
            this.from = from;
            this.to = to;
            this.w = w;
        }
    }
    static class SNode implements Comparable<SNode> {
        int id;
        long n_dis;
        public SNode(int id, long n_dis) {
            this.id = id;
            this.n_dis = n_dis;
        }
        @Override
        public int compareTo(SNode o) {
            return Long.compare(n_dis, o.n_dis);
        }
    }
    static List<Edge>[] e = new List[N];
    static long[] dis = new long[N];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        for (int i = 1; i <= n; i++)  e[i] = new ArrayList<>();        
        while (m-- > 0) {
            int u = sc.nextInt();
            int v = sc.nextInt();
            long w = sc.nextLong();
            e[u].add(new Edge(u, v, w));
        }
        dijkstra(n);
        for (int i = 1; i <= n; i++) {
            if (dis[i] >= INF)    System.out.print("-1 ");
            else                  System.out.printf("%d ", dis[i]);            
        }
    }
    static void dijkstra(int n) {
        int s = 1;
        boolean[] done = new boolean[N];
        Arrays.fill(dis, INF);
        dis[s] = 0;
        PriorityQueue<SNode> queue = new PriorityQueue<>();
        queue.offer(new SNode(s, dis[s]));
        while (!queue.isEmpty()) {
            SNode u = queue.poll();
            if (done[u.id])   continue;            
            done[u.id] = true;
            for (Edge edge : e[u.id]) {
                if (done[edge.to])      continue;                
                if (dis[edge.to] > edge.w + u.n_dis) {
                    dis[edge.to] = edge.w + u.n_dis;
                    queue.offer(new SNode(edge.to, dis[edge.to]));
                }
            }
        }
    }
}

Python代码
  用堆heapq实现优先队列。另外注意邻接表的用法,24行读取u的邻居v和边长w,12行遍历u的邻居v和边长w。

import array, heapq
def dij(s):
    done = [0 for i in range(n + 1)]
    hp = []
    dis[s] = 0
    heapq.heappush(hp, (0, s))
    while hp:
        u = heapq.heappop(hp)[1]
        if done[u]:	continue
        done[u] = 1
        for i in range(len(G[u])):
            v, w = G[u][i]            #遍历u的邻居v,边长w
            if done[v]: continue
            if dis[v] > dis[u] + w:
                dis[v] = dis[u] + w
                heapq.heappush(hp, (dis[v], v))
n, m = map(int, input().split())
s = 1
G = [[] for i in range(n+1)]          #邻接表存图
INF = 1<<64
dis=[INF]*(n+1)
for i in range(m):
    u, v, w = map(int, input().split())
    G[u].append((v,w))                #存图,u的一个邻居是v,边长w
dij(s)
for i in range(1, n + 1):
    if dis[i]>=INF: print("-1",end=' ')
    else: print(dis[i], end=' ')

4. 练习题

洛谷的最短路题目,按难度排序:
https://www.luogu.com.cn/problem/list?tag=160&orderBy=difficulty&order=asc&page=1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1415218.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单片机学习笔记---独立按键控制LED亮灭

直接进入正题&#xff01; 今天开始我们要学习一个新的模块&#xff1a;独立按键&#xff01; 先说独立按键的内部结构&#xff1a; 它相当于一种电子开关&#xff0c;按下时开关接通&#xff0c;松开时开关断开&#xff0c;实现原理是通过轻触按键内部的金属弹片受力弹动来实…

深度学习知识

context阶段和generation阶段的不同 context阶段&#xff08;又称 Encoder&#xff09;主要对输入编码&#xff0c;产生 CacheKV(CacheKV 实际上记录的是 Transformer 中 Attention 模块中 Key 和 Value 的值&#xff09;&#xff0c;在计算完 logits 之后会接一个Sampling 采…

CC++内存管理【非常详细,对新手友好】

文章目录 一、程序内存划分1.基础知识2. 堆栈的区别3. 题目练手 二、C语言中动态内存管理方式三、C中动态内存管理方式1. new/delete操作内置类型2. new/delete操作自定义类型 四、operator new和operator delete函数1. 汇编查看编译器底层调用2. 透过源码分析两个全局函数 五、…

GD32移植FreeRTOS+CLI过程记录

背景 之前我只在STM32F0上基于HAL库和CubeMX移植FreeRTOS&#xff0c;但最近发现国产化替代热潮正盛&#xff0c;许多项目都有国产化器件指标&#xff0c;而且国产单片机确实比意法的便宜&#xff0c;所以也买了块兆易创新的GD32F303开发板&#xff0c;试一试它的优劣。虽然GD…

【开源】基于JAVA的班级考勤管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统基础支持模块2.2 班级学生教师支持模块2.3 考勤签到管理2.4 学生请假管理 三、系统设计3.1 功能设计3.1.1 系统基础支持模块3.1.2 班级学生教师档案模块3.1.3 考勤签到管理模块3.1.4 学生请假管理模块 3.2 数据库设…

【系统备份/迁移】解决克隆win10系统分区后,进系统黑屏、有鼠标指针(无需修改注册表)

【解法】简单来说就是&#xff0c;在PE系统中修复引导&#xff0c;修复成功后再进入系统就正常了。 1、问题 笔者通过DiskGenius克隆系统分区来备份自己的win10系统。克隆完成后&#xff0c;进入新系统里&#xff0c;发现是黑屏&#xff0c;移动鼠标时可以看到鼠标指针&#x…

通过铭文赛道的深度链接,XDIN3 与 opBNB 的双向奔赴

​进入到 2024 年以来&#xff0c;随着铭文市场基建设施的不断完善&#xff0c;铭文正在被赋予捕获价值与流动性的能力&#xff0c;并且铭文投资者们也正在趋于理性&#xff0c;这也意味着铭文赛道正在向价值回归的全新方向发展。 XDIN3 是推动铭文资产捕获价值的重要基建设施&…

第7章 面向对象基础(下)

第7章 面向对象基础&#xff08;下&#xff09; 学习目标 会区分静态的类变量和非静态的实例变量 会区分静态的类方法和非静态的实例方法 了解类初始化 认识枚举类型 会使用枚举类型 认识包装类 会使用包装类进行处理字符串 会分析包装类的相关面试题 能够声明抽象类 能够说出…

ZigBee学习——浅析协议栈

✨记录学习过程 文章目录 一、初识OSAL1.1 Z-Stack和Zigbee的OSAL是什么关系&#xff1f;1.2 OSAL可以解决Z-stack在不同厂商的芯片上的使用吗&#xff1f; 二、协议栈运行机制2.1 初始化涉及内容2.2 初始化过程 一、初识OSAL OSAL&#xff0c;全称是操作系统抽象层&#xff0…

【驱动系列】C#获取电脑硬件显卡核心代号信息

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《驱动系列》文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点…

代码随想录算法训练营day4 | 链表(2)

一、LeetCode 24 两两交换链表中的节点 题目链接&#xff1a;24.两两交换链表中的节点https://leetcode.cn/problems/swap-nodes-in-pairs/ 思路&#xff1a;设置快慢指针&#xff0c;暂存节点逐对进行交换。 代码优化前&#xff1a; /*** Definition for singly-linked list…

总结和考试

总结和考试 1. 代码规范1.1 名称1.2 注释1.3 todo1.4 条件嵌套1.5 简单逻辑先处理1.6 循环1.7 变量和值 2.知识补充2.1 pass2.2 is 比较2.3 位运算 3.阶段总结4.考试题 1. 代码规范 程序员写代码是有规范的&#xff0c;不只是实现功能而已。 1.1 名称 在Python开发过程中会创…

EndNote20 添加GBT7714文献格式

GBT 7714格式是中国国家标准《文后参考文献著录规则》的规定&#xff0c;用于指导学术论文、期刊文章等文献的参考文献著录。GBT 7714标准规定了参考文献的格式、内容和著录要求&#xff0c;以确保文献的一致性和标准化。 在EndNote 20中&#xff0c;若需要按照GBT 7714格式在W…

JavaScript 之 作用域变量提升闭包

一、JavaScript 代码的执行 浏览器内核是由两部分组成的&#xff0c;以 webkit 为例 WebCore&#xff1a;负责HTML解析、布局、渲染等等相关的工作JavaScriptCore&#xff1a;解析、执行 JavaScript 代码 另外一个强大的 JavaScript 引擎就是 V8 引擎 二、深入 V8 引擎原理 …

Django介绍

一、介绍 Django是Python语言中的一个Web框架,Python语言中主流的web框架有Django、Tornado、Flask 等多种 优势:大而全,框架本身集成了ORM、模型绑定、模板引擎、缓存、Session等功能,是一个全能型框架,拥有自己的Admin数据管理后台,第三方工具齐全,性能折中 缺点:…

用ChatGPT写申请文书写进常春藤联盟?

一年前&#xff0c;ChatGPT 的发布引发了教育工作者的恐慌。现在&#xff0c;各大学正值大学申请季&#xff0c;担心学生会利用人工智能工具伪造入学论文。但是&#xff0c;聊天机器人创作的论文足以骗过大学招生顾问吗&#xff1f; ChatGPT简介 ChatGPT&#xff0c;全称聊天生…

C++:引用

目录 概念&#xff1a; 引用的使用格式&#xff1a; 引用特性&#xff1a; 常引用 使用场景&#xff1a; 1、做参数 二级指针时的取别名 一级指针取别名 一般函数取别名 2、做返回值 函数返回值的原理&#xff1a; 引用的返回值使用&#xff1a; 引用和指针的对比&…

基于 SpringBoot+Vue 的免税商品商城系统的研究与实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

免费电视TV盒子软件,好用的免费电视盒子软件大全,免费电视盒子APP大全,2024最新整理

1、TVbox下载地址、影视接口、配置教程 下载地址 TVbox TVbox可用接口地址合集 注&#xff1a;接口均来源于互联网收集分享&#xff01;所有接口都是经过测试的&#xff0c;如果出现加载失败等情况&#xff0c;可能是因为接口针对的盒子有兼容问题&#xff0c;可以多试试几…

(七)springboot实战——springboot3集成R2DBC实现webflux响应式编程服务案例

前言 本节主要内容是关于使用新版springboot3集成响应式数据库R2DBC,完成响应式web服务案例。需要注意的是&#xff0c;此次项目使用的JDK版本是JDK17&#xff0c;springboot版本使用3.2.2版本&#xff0c;数据库使用关系型数据库mysql。WebFlux 是一个基于响应式编程模型的框…