题目链接:

难度一:P3371 【模板】单源最短路径(弱化版)

难度二:P4779 【模板】单源最短路径(标准版) (慎入)

题目简述 给出一个有向图,请输出从某一点出发到所有点的最短路径长度。

 

输入格式: 第一行包含三个整数N、M、S,分别表示点的个数、有向边的个数、出发点的编号。

        接下来M行每行包含三个整数Fi、Gi、Wi,分别表示第i条有向边的出发点、目标点和长度。

输出格式 :仅一行,包含N个用空格分隔的整数,其中第i个整数表示从点S出发到点i的最短路径长度(若S=i则最短路径长度为0,若从点S无法到达点i,则最短路径长度为2147483647)

样例数据

输入:

4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4

输出:

0 2 4 3

 

第一种:Floyd算法

  思想:

    Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)

    从任意节点i到任意节点j的最短路径不外乎2种可能,一是直接从i到j;二是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。

 

  优点:

    容易理解,可以算出任意两个节点之间的最短距离,代码编写简单。并可以处理负边权问题

 

  缺点:

    时间复杂度比较高,不适合计算大量数据。时间复杂度O(n^3),空间复杂度O(n^2)。且不能处理负环回路问题

 

  初始化&&输入:

      Floyd常用邻接矩阵(二维数组)来存储;即F[i][j]存储从点 i 到点 j 的距离dis

     值得一提的是:在初始化时,要将所有的点存为+∞,表示从点 i 到点 j 无法到达,再将F[i][j]赋值为 0(i==j)

     所有的 Dis[i]=MAXN;Dis[S]=0;//详见下楼代码

  代码实现:

1 for(L k=1; k<=N; ++k)//枚举中间点!!!最关键的点
2     for(L i=1; i<=N; ++i )//枚举起点
3          for(L j=1; j<=N; ++j)//枚举终点
4                 if(F[i][j]+ANS[i]<ANS[j]) //若经过中间点的距离小于已知最短的距离!!!
5                   ANS[j]=F[i][j]+ANS[i];//更新数据                  

  完整代码:

 1 //Floyd版
 2 #include<algorithm>
 3 #include<cstdio>
 4 #include<cstdlib>
 5 #include<cstring>
 6 #include<iostream>
 7 using namespace std;
 8 typedef long long L;
 9 const L MAXN=2147483647;
10 L F[10010][10010],ANS[10010];
11 inline L read()//快速读入,PS:可以直接用scanf,此次是笔者打习惯了
12 {
13     L w=0,x=0;
14     char ch=0;
15     while(!isdigit(ch))w|=ch=='-',ch=getchar();
16     while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
17     return w?-x:x;
18 }
19 int main(void)
20 {
21     L N=read(),M=read(),S=read();
22     for(L i=1; i<=N; ++i)//初始化
23     {
24         for(L j=1; j<=N; ++j) F[i][j]=(i==j?0:MAXN);
25         ANS[i]=MAXN;
26     }
27     for(L i=1; i<=M; ++i)//输入
28     {
29         L A=read(),B=read(),l=read();
30         F[A][B]=min(F[A][B],l);
31     }
32     ANS[S]=0,F[1][1]=0;
33     for(L k=1; k<=N; ++k)//Floyd算法实现
34          for(L i=1; i<=N; ++i )
35               for(L j=1; j<=N; ++j)
36                   if(F[i][j]+ANS[i]<ANS[j]) 
37                     ANS[j]=F[i][j]+ANS[i];
38     for(L i=1; i<=N; ++i) printf("%lld ",ANS[i]);//输出
39     return 0;
40 }
41         

 

   评价:

    这种算法虽然时间复杂度较高,但胜在容易理解,并且代码简便,清晰。在其他算法不会时可以骗个30分。(⊙o⊙)嗯!!!

 

 第二种:Dijkstra算法

  基本思想:

    Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

  算法步骤:

    1.初始时, From 只包含源点,即 From={S} 。 U 包含除 S 外的其他顶点,即:U={其余顶点}。 f 为 From 内一点, u 为 U 内一点。若从 f 能到 u 则 MAP[i] [j]!=MAXN,若f不能到 u ,则 MAP[i][j] 权值为 MAXN 。

    2.从 U 中选取一个距离 S 最小的顶点 f ,把 f ,加入 From 中(该选定的距离就是 S 到 f 的最短路径长度)。

    3.以  f  为新考虑的中间点,修改 U 中各顶点的距离:若从起点 S 到顶点 u 的距离(经过顶点 f )比原来距离(不经过顶点 f )短,则修改顶点 u 的距离值,修改后的距离值的顶点 f 的距离加上边上的权。

      4.重复步骤 2 和 3 直到所有顶点都包含在 From 中。

    论最短路径问题!!!(NOIP必备)

  基础版:

      1.邻接矩阵存储:

        

//Dijkstra版(未优化)
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long L;
const L MAXN=2147483647;
L MAP[10010][10010],Dis[10010];
bool from[10010];
inline L read()   //快读
{
    L w=0,x=0;
    char ch=0;
    while(!isdigit(ch))w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return w?-x:x;
}
int main(void)
{
    L N=read(),M=read(),S=read();
    for(L i=1; i<=N; ++i)//初始化
    {
        for(L j=1; j<=N; ++j) MAP[i][j]=(i==j?0:MAXN);
        Dis[i]=(i==S)?0:MAXN;
    }
    for(L i=1; i<=M; ++i) //输入
    {
        L f=read(),to=read(),dis=read();
        MAP[f][to]=min(dis,MAP[f][to]);
    }
    for(L i=1; i<N; ++i)//共有N个顶点,除终点外还有N-1 个顶点 
    {
        L MINN=MAXN,f=0;
        for(L j=1; j<=N; ++j) //查找从S到u的最短路 
            if( (!from[j] ) && (Dis[j]<MINN) )//(!from[j])因为必须是从U中取出的顶点 
              MINN=Dis[j],f=j;
        from[f]=true;//记录这点已在From中
        for(L j=1; j<=N; ++j)
            Dis[j]=(Dis[f]+MAP[f][j]<Dis[j])?Dis[f]+MAP[f][j]:Dis[j];//更新数据 
    }
    for(L i=1; i<=N; ++i) printf("%lld ",Dis[i]);//输出 
    return 0;
}            

      2.链式前向星存储:(链式前向星可以减少存储空间,减少遍历次数链式前向星的使用

 1 #include<algorithm>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #include<iostream>
 6 using namespace std;
 7 typedef long long L;
 8 const L MAXN=2147483647;
 9 struct Edge//前向星 
10 {
11     L next;//下一条边的编号
12     L to;//这条边到达的点
13     L dis;//这条边的长度
14 } E[500010];
15 L num_edge=0,Head[10010],Dis[10010];
16 bool from[10010];
17 inline L read()   //快读
18 {
19     L w=0,x=0;
20     char ch=0;
21     while(!isdigit(ch))w|=ch=='-',ch=getchar();
22     while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
23     return w?-x:x;
24 }
25 inline void Add_edge(L from,L to,L d)//前向星插入 
26 {
27     ++num_edge;
28     E[num_edge].next=Head[from];
29     E[num_edge].to=to;
30     E[num_edge].dis=d;
31     Head[from]=num_edge;
32 }
33 int main(void)
34 {
35     L N=read(),M=read(),S=read();
36     for(int i=1; i<=N; ++i) Dis[i]=(i==S)?0:MAXN;//初始化 
37     for(L i=1; i<=M; ++i)//输入 
38     {
39         L u=read(),v=read(),d=read();
40         Add_edge(u,v,d);
41     }  
42     L f=S;
43     while(!from[f])
44     {
45         from[f]=true;
46         
47         for(L i=Head[f]; i!=0; i=E[i].next)//修改点距离 
48             if( !from[E[i].to ] && (Dis[f]+E[i].dis<Dis[E[i].to]) )
49                 Dis[E[i].to]=Dis[f]+E[i].dis;//更新 
50                 
51         L MINN=MAXN;
52         for(L i=1; i<=N; ++i)//寻找最小点 
53         {
54 
55             if(!from[i]&&Dis[i]<MINN)
56             {
57                 MINN=Dis[i];
58                 f=i;
59             }
60         }
61     }
62     for(L i=1; i<=N; i++)printf("%lld ",Dis[i]);//输出 
63     return 0;
64 }

 

  优先队列(堆)优化版:

    其中的 pair  参见: C++ std::pair的用法

    priority_queue优先队列参见:STL--priority_queue用法

    堆优化的原理就是,将寻找当前最小路径的结点这一操作,交由优先队列来执行

    若是用大根堆,则在make_pair(-Dis[E[i].to],E[i].to);

    用小根堆,则make_pair(Dis[E[i].to,E[i].to);
//Dijkstra版(堆优化)
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
typedef pair<int,int> node;
const int MAXN=2147483647;
struct Edge
{
    int next;//下一条边的编号
    int to;//这条边到达的点
    int dis;//这条边的长度
} E[200010];
int num_edge=0,Head[100010],Dis[100010],N,M,S;
bool Map[100010];
inline int read()   //快读
{
    int w=0,x=0;
    char ch=0;
    while(!isdigit(ch))w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return w?-x:x;
}
inline void Add_edge(int from,int to,int d)//前向星存储试插入
{
    ++num_edge;
    E[num_edge].next=Head[from];
    E[num_edge].to=to;
    E[num_edge].dis=d;
    Head[from]=num_edge;
}
inline void Scanf()//输入与初始化
{
    N=read(),M=read(),S=read();
    for(int i=1; i<=N; ++i) Dis[i]=MAXN;
    Dis[S]=0;
    for(int i=1; i<=M; ++i)
    {
        int u=read(),v=read(),d=read();
        Add_edge(u,v,d);
    }
}
inline void Printf()//输出答案
{
    for(int i=1; i<=N; i++)printf("%d ",Dis[i]);

}
inline void Dijkstra()
{
    priority_queue< node,vector<node>,greater<node> > T;
    T.push(make_pair(0,S));
    int head;
    while(!T.empty())
    {
        head=T.top().second;
        T.pop();
        if(Map[head])continue;
        else Map[head]=true;
         for(int i=Head[head]; i!=0; i=E[i].next)
        {
            if(Dis[head]+E[i].dis<Dis[E[i].to])
            {
                Dis[E[i].to]=Dis[head]+E[i].dis;
                T.push(make_pair(Dis[E[i].to],E[i].to)); 
            }
        }
    }
}
int main(void)
{
    Scanf();
    Dijkstra();
    Printf();
    return 0;
}

 

 

 

 第三种:Bellman-ford算法

  基本思想:

 

    Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

  算法步骤:

 

  1. 初始时, From 只包含源点,即 From={S} 。 U 包含除 S 外的其他顶点,即:U={其余顶点}。 f 为 From 内一点, u 为 U 内一点。若从 f 能到 u 则 MAP[i] [j]!=MAXN,若f不能到 u ,则 MAP[i][j] 权值为 MAXN 。
  2. 从 U 中选取一个距离 S 最小的顶点 f ,把 f ,加入 From 中(该选定的距离就是 S 到 f 的最短路径长度)。
  3. 以  f  为新考虑的中间点,修改 U 中各顶点的距离:若从起点 S 到顶点 u 的距离(经过顶点 f )比原来距离(不经过顶点 f )短,则修改顶点 u 的距离值,修改后的距离值的顶点 f 的距离加上边上的权。
  4. 重复步骤 2 和 3 直到所有顶点都包含在 From 中。

 第四种:SPFA算法(队列优化的Bellman-ford)

 

 1 #include<bits/stdc++.h>
 2 const long long inf=2147483647;
 3 const int maxn=10005;
 4 const int maxm=500005;
 5 using namespace std;
 6 int n,m,s,num_edge=0;
 7 int dis[maxn],vis[maxn],head[maxm];
 8 struct Edge
 9 {
10   int next,to,dis;
11 }edge[maxm]; //结构体表示静态邻接表
12 void addedge(int from,int to,int dis) //邻接表建图
13 { //以下是数据结构书上的标准代码,不懂翻书看解释
14   edge[++num_edge].next=head[from]; //链式存储下一条出边
15   edge[num_edge].to=to; //当前节点编号
16   edge[num_edge].dis=dis; //本条边的距离
17   head[from]=num_edge; //记录下一次的出边情况
18 }
19 void spfa()
20 {
21   queue<int> q; //spfa用队列,这里用了STL的标准队列
22   for(int i=1; i<=n; i++) 
23   {
24     dis[i]=inf; //带权图初始化
25     vis[i]=0; //记录点i是否在队列中,同dijkstra算法中的visited数组
26   }
27   q.push(s); dis[s]=0; vis[s]=1; //第一个顶点入队,进行标记
28   while(!q.empty())
29   {
30     int u=q.front(); //取出队首
31     q.pop(); vis[u]=0; //出队标记
32     for(int i=head[u]; i; i=edge[i].next) //邻接表遍历,不多解释了(也可用vector代替)
33     {
34       int v=edge[i].to; 
35       if(dis[v]>dis[u]+edge[i].dis) //如果有最短路就更改
36       {
37         dis[v]=dis[u]+edge[i].dis;
38         if(vis[v]==0) //未入队则入队
39         {
40           vis[v]=1; //标记入队
41           q.push(v);
42         }
43       }
44     }
45   }
46 }
47 int main()
48 {
49   cin>>n>>m>>s;
50   for(int i=1; i<=m; i++)
51   {
52     int f,g,w;
53     cin>>f>>g>>w; 
54     addedge(f,g,w); //建图,有向图连一次边就可以了
55   }
56   spfa(); //开始跑spfa
57   for(int i=1; i<=n; i++)
58     if(s==i) cout<<0<<" "; //如果是回到自己,直接输出0
59       else cout<<dis[i]<<" "; //否则打印最短距离
60   return 0;
61 } //结束
View Code

相关文章:

  • 2021-11-30
  • 2022-01-05
  • 2021-05-27
猜你喜欢
  • 2021-12-31
  • 2022-01-15
  • 2022-12-23
  • 2022-12-23
  • 2021-11-04
相关资源
相似解决方案