• 最短路算法


    例题:FroggerPOJ - 2253  链接 https://vjudge.net/problem/POJ-2253

    1. Floyd算法

    算法思想:用mp[i][j]来表示i到j之间的距离,先对mp数组初始化,然后根据输入录入已知的i和j之间的距离,然后三重循环,第一重用k表示i和j之外的数,第二重为i,第三重为j。改变的思路是如果i到j之间的距离比i到k加上k到j之间的距离,则改变大小。时间复杂度为O(n^3).适合n比较小的题。

    代码:

     1 #include <cstdio>
     2 #include <fstream>
     3 #include <algorithm>
     4 #include <cmath>
     5 #include <deque>
     6 #include <vector>
     7 #include <queue>
     8 #include <string>
     9 #include <cstring>
    10 #include <map>
    11 #include <stack>
    12 #include <set>
    13 #include <sstream>
    14 #include <iostream>
    15 #define mod 998244353
    16 #define eps 1e-6
    17 #define ll long long
    18 #define INF 0x3f3f3f3f
    19 using namespace std;
    20 
    21 //ma用来存放两个石头之间的距离
    22 double ma[210][210];
    23 int main()
    24 {
    25     //x,y数组存放地点
    26     double x[210],y[210];
    27     int n,ans=1;
    28     //n为0时退出
    29     while(scanf("%d",&n)&&n)
    30     {
    31         int a,b;
    32         for(int i=1;i<=n;i++)
    33         {
    34             scanf("%lf %lf",&x[i],&y[i]);
    35         }
    36         //初始化ma数组为0,是为了保证求的是最大距离中的小距离
    37         memset(ma,0,sizeof(ma));
    38         for(int i=i=1;i<=n;i++)
    39         {
    40             for(int j=1;j<=n;j++)
    41             {
    42                 //录入两个石头之间的距离
    43                 ma[i][j]=ma[j][i]=sqrt(pow(x[i]-x[j],2.0)+pow(y[i]-y[j],2.0));
    44             }
    45         }
    46         //Floyd算法核心
    47         for(int k=1;k<=n;k++)
    48         {
    49             for(int i=1;i<=n;i++)
    50             {
    51                 for(int j=1;j<=n;j++)
    52                 {
    53                     //许多通路中最大距离中的最小距离
    54                     ma[i][j]=min(ma[i][j],max(ma[i][k],ma[k][j]));
    55                 }
    56             }
    57         }
    58         printf("Scenario #%d
    Frog Distance = %.3lf
    
    ",ans++,ma[1][2]);
    59     }
    60 
    61 }

    2.Digkstra算法

      迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。

      问题描述:一个有权图,Dijkstra算法可以计算任意节点到其他节点的最短路径

    算法思路

    1. 指定一个节点,例如我们要计算 'A' 到其他节点的最短路径
    2. 引入两个集合(S、U),S集合包含已求出的最短路径的点(以及相应的最短长度),U集合包含未求出最短路径的点(以及A到该点的路径,注意 如上图所示,A->C由于没有直接相连 初始时为∞
    3. 初始化两个集合,S集合初始时 只有当前要计算的节点,A->A = 0
      U集合初始时为 A->B = 4, A->C = ∞, A->D = 2, A->E = ∞敲黑板!!!接下来要进行核心两步骤了
    4. 从U集合中找出路径最短的点,加入S集合,例如 A->D = 2
    5. 更新U集合路径,if ( 'D 到 B,C,E 的距离' + 'AD 距离' < 'A 到 B,C,E 的距离' ) 则更新U
    6. 循环执行 4、5 两步骤,直至遍历结束,得到A 到其他节点的最短路径

    算法图解

    1.选定A节点并初始化,如上述步骤3所示
     
     

    2.执行上述 4、5两步骤,找出U集合中路径最短的节点D 加入S集合,并根据条件 if ( 'D 到 B,C,E 的距离' + 'AD 距离' < 'A 到 B,C,E 的距离' ) 来更新U集合

     
     

    3.这时候 A->B, A->C 都为3,没关系。其实这时候他俩都是最短距离,如果从算法逻辑来讲的话,会先取到B点。而这个时候 if 条件变成了 if ( 'B 到 C,E 的距离' + 'AB 距离' < 'A 到 C,E 的距离' ) ,如图所示这时候A->B距离 其实为 A->D->B

     
     
    1. 思路就是这样,往后就是大同小异了
     
     
    1. 算法结束
     
     
     
    代码:
     1 #include <cstdio>
     2 #include <fstream>
     3 #include <algorithm>
     4 #include <cmath>
     5 #include <deque>
     6 #include <vector>
     7 #include <queue>
     8 #include <string>
     9 #include <cstring>
    10 #include <map>
    11 #include <stack>
    12 #include <set>
    13 #include <sstream>
    14 #include <iostream>
    15 #define mod 998244353
    16 #define eps 1e-6
    17 #define ll long long
    18 #define INF 0x3f3f3f3f
    19 using namespace std;
    20 
    21 //dis用来存放到1之间的距离
    22 double dis[310];
    23 //ma用来存放两个石头之间的距离
    24 double ma[210][210];
    25 void dijkstra(int m)
    26 {
    27     int i;
    28     //vis数组标记已经找过的最短路
    29     bool vis[1010];
    30     memset(vis,0,sizeof(vis));
    31     for(int i=1;i<=m;i++)
    32     {
    33         dis[i]=INF;
    34     }
    35     dis[1]=0;
    36     for(int i=1;i<=m;i++)
    37     {
    38         //mi记录当前到1距离最小的值
    39         double mi=INF;
    40         //k表示到1距离最小的点
    41         int k=1;
    42         for(int j=1;j<=m;j++)
    43         {
    44             //当未标记这个点,并且这个点到1的距离时最小的时侯成立,
    45             if(!vis[j]&&mi>dis[j])
    46             {
    47                 mi=dis[j];
    48                 k=j;
    49             }
    50         }
    51         //已找到1到k点的最小值,所以标记这个点
    52         vis[k]=1;
    53         for(int j=1;j<=m;j++)
    54         {
    55             //所有通路中最大距离中的最小数
    56             dis[j]=min(dis[j],max(dis[k],ma[k][j]));
    57         }
    58     }
    59 }
    60 
    61 int main()
    62 {
    63     //x,y数组存放地点
    64     double x[210],y[210];
    65     int n,ans=1;
    66     //n为0时退出
    67     while(scanf("%d",&n)&&n)
    68     {
    69         int a,b;
    70         for(int i=1;i<=n;i++)
    71         {
    72             scanf("%lf %lf",&x[i],&y[i]);
    73         }
    74         //初始化ma数组为0,是为了保证求的是最大距离中的小距离
    75         memset(ma,0,sizeof(ma));
    76         for(int i=i=1;i<=n;i++)
    77         {
    78             for(int j=1;j<=n;j++)
    79             {
    80                 //录入两个石头之间的距离
    81                 ma[i][j]=ma[j][i]=sqrt(pow(x[i]-x[j],2.0)+pow(y[i]-y[j],2.0));
    82             }
    83         }
    84         dijkstra(n);
    85         printf("Scenario #%d
    Frog Distance = %.3lf
    
    ",ans++,dis[2]);
    86     }
    87 
    88 }

     3.SPFA算法

    算法优点

            1.时间复杂度比普通的Dijkstra和Ford

            2.能够计算负权图问题。

            3.能够判断是否有负环 (即:每跑一圈,路径会减小,所以会一直循环跑下去)。

    算法思想:

            我们用数组记录每个结点的最短路径估计值,用邻接表来存储图G。

            我们采取的方法是动态逼近法:

                    1.设立一个先进先出的队列用来保存待优化的结点。

                    2.优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。

                    3.这样不断从队列中取出结点来进行松弛操作,直至队列空为止

    期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。

    实现方法:

      1.存入图。可以使用链式前向星或者vocter

            2.开一个队列,先将开始的节点放入。

            3.每次从队列中取出一个节点X,遍历与X相通的Y节点,查询比对  Y的长度 和 X的长度+ X与Y的长度

                如果X的长度+ X与Y的长度 Y的长度,说明需要更新操作。

                        1).存入最短路。

                        2).由于改变了原有的长度,所以需要往后更新,与这个节点相连的最短路。(即:判断下是否在队列,在就不用重复,不在就加入队列,等待更新)。

                        3).在这期间可以记录这个节点的进队次数,判断是否存在负环。

            4.直到队空。

    判断有无负环:如果某个点进入队列的次数超过N次则存在负环

    模拟过程:

    首先建立起始点a到其余各点的最短路径表格

                                      

    首先源点a入队,当队列非空时:

            1、队首元素(a)出队,对以a为起始点的所有边的终点依次进行松弛操作(此处有b,c,d三个点),此时路径表格状态为:

                                      

    在松弛时三个点的最短路径估值变小了,而这些点队列中都没有出现,这些点需要入队,此时,队列中新入队了三个结点b,c,d

       2,队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e点),此时路径表格状态为:

                                     

    在最短路径表中,e的最短路径估值也变小了,e在队列中不存在,因此e也要入队,此时队列中的元素为c,d,e

      3,队首元素c点出队,对以c为起始点的所有边的终点依次进行松弛操作(此处有e,f两个点),此时路径表格状态为:

                                     

    在最短路径表中,e,f的最短路径估值变小了,e在队列中存在,f不存在。因此e不用入队了,f要入队,此时队列中的元素为d,e,f

       4,队首元素d点出队,对以d为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:

                                   

    在最短路径表中,g的最短路径估值没有变小(松弛不成功),没有新结点入队,队列中元素为f,g

      5,队首元素f点出队,对以f为起始点的所有边的终点依次进行松弛操作(此处有d,e,g三个点),此时路径表格状态为:

                                   

    在最短路径表中,e,g的最短路径估值又变小,队列中无e点,e入队,队列中存在g这个点,g不用入队,此时队列中元素为g,e

      6,队首元素g点出队,对以g为起始点的所有边的终点依次进行松弛操作(此处只有b点),此时路径表格状态为:

                               

    在最短路径表中,b的最短路径估值又变小,队列中无b点,b入队,此时队列中元素为e,

      7,b队首元素e点出队,对以e为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:

                              

    在最短路径表中,g的最短路径估值没变化(松弛不成功),此时队列中元素为b

      8,队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e这个点),此时路径表格状态为:

                             

    在最短路径表中,e的最短路径估值没变化(松弛不成功),此时队列为空了

    最终a到g的最短路径为14

    例题:

    https://www.cnblogs.com/mzchuan/p/11521348.html

    代码:

     1 #include <cstdio>
     2 #include <fstream>
     3 #include <algorithm>
     4 #include <cmath>
     5 #include <deque>
     6 #include <vector>
     7 #include <queue>
     8 #include <string>
     9 #include <cstring>
    10 #include <map>
    11 #include <stack>
    12 #include <set>
    13 #include <sstream>
    14 #include <iostream>
    15 #define mod 998244353
    16 #define eps 1e-6
    17 #define ll long long
    18 #define INF 0x3f3f3f3f
    19 using namespace std;
    20 const int maxn=150010;
    21 //n,m代表点数和边数
    22 int n,m;
    23 //x代表与之相连的点,y代表边的价值,next代表当前边起点的位置
    24 struct node
    25 {
    26     int x,y,next;
    27 };
    28 //no数组存放边的数据
    29 node no[maxn];
    30 //head存放每条边的位置,ans表示第几条边
    31 int head[maxn],ans;
    32 //dis表示起点到其他边的最短长度
    33 int dis[30010];
    34 //vis表示此点是否已是最短路
    35 bool vis[30010];
    36 void spfa()
    37 {
    38     //stackbiqueue更节约时间,
    39     stack<int> qu;
    40     qu.push(1);
    41     //初始化
    42     memset(dis,INF,sizeof(dis));
    43     memset(vis,0,sizeof(vis));
    44     vis[1]=1;
    45     dis[1]=0;
    46     //为空时退出
    47     while(!qu.empty())
    48     {
    49         int s=qu.top();
    50         qu.pop();
    51         //由于当钱点数据一边,所以要更新与之相连的所有点直接按的距离
    52         vis[s]=0;
    53         //从当前点开始直到与之相连的点
    54         for(int i=head[s];i!=-1;i=no[i].next)
    55         {
    56             //en表示与起点相连的点
    57             int en = no[i].x;
    58             //更新最小操作
    59             if(dis[en]>dis[s]+no[i].y)
    60             {
    61                 dis[en]=dis[s]+no[i].y;
    62                 if(!vis[en])
    63                 {
    64                     vis[en]=1;
    65                     qu.push(en);
    66                 }
    67             }
    68         }
    69     }
    70 }
    71 
    72 int main()
    73 {
    74     ans=0;
    75     scanf("%d %d",&n,&m);
    76     int a,b,c;
    77     //初始化
    78     memset(head,-1,sizeof(head));
    79     for(int i=0;i<m;i++)
    80     {
    81         scanf("%d %d %d",&a,&b,&c);
    82         //存放边
    83         no[ans].x=b;
    84         no[ans].y=c;
    85         no[ans].next=head[a];
    86         head[a]=ans++;
    87     }
    88     spfa();
    89     printf("%d
    ",dis[n]);
    90 }

     4.Bellman-Ford算法

    算法作用:(主要用来求是否有正负环)

    算法思想:Bellman-Ford 算法计算最短路径的过程中,使用了上述的松弛函数,通过对路径的不断松弛,来逐渐获取最短路径。

    Bellman-Ford 算法可以检测带权有向图中是否存在负权回路,由松弛函数可知正常情况下,如果图中不存在负权回路,那么即使在最坏情况下,也只需要执行 |V|-1次迭代松弛,即可获得从起点到各顶点的最短路径。

    若图中存在负权回路,当回路较小时,例如顶点自身或者两个顶点之间的负权回路,则在 |V|-1 次迭代过程中,可能多次通过了该负权回路;若回路较大,例如从起点出发,串联所有顶点最后回到起点,即通过 |V|-1 条边构成一个圆形,如下图所示。则 |V|-1 次迭代过程中,可能一次也不会通过该负权回路,但是当再执行一次迭代松弛,即可将 d[s] 值更新为负值,所以可以多执行一次迭代,通过判断是否更新从起点到某个顶点的最短路径权值,来判断图中是否存在负权回路。

     
    例题:https://www.cnblogs.com/mzchuan/p/11490895.html

    代码:

      1 #include <cstdio>
      2 #include <fstream>
      3 #include <algorithm>
      4 #include <cmath>
      5 #include <deque>
      6 #include <vector>
      7 #include <queue>
      8 #include <string>
      9 #include <cstring>
     10 #include <map>
     11 #include <stack>
     12 #include <set>
     13 #include <sstream>
     14 #include <iostream>
     15 #define mod 998244353
     16 #define eps 1e-6
     17 #define ll long long
     18 #define INF 0x3f3f3f3f
     19 using namespace std;
     20 
     21 //用于存放货币的转换
     22 struct node
     23 {
     24     //x源来的钱的种类,b转换后钱的种类
     25     int x,y;
     26     //s表示汇率
     27     double s;
     28 };
     29 //ve表示有多少个汇率
     30 vector<node> ve;
     31 //mp用于给货币种类编号
     32 map<string,int> mp;
     33 //dis表示起点转换成其他点后的钱数
     34 double dis[35];
     35 //bellman用于判断是否有正环
     36 //n表示有了n个点,v表示起点
     37 bool bellman(int n,int v)
     38 {
     39     //初始钱数为0;
     40     for(int i=1;i<=n;i++)
     41     {
     42         dis[i]=0;
     43     }
     44     //将起点的钱数设为1,为方便计算
     45     dis[v]=1;
     46     //n-1次遍历
     47     for(int i=1;i<n;i++)
     48     {
     49         //对每个汇率进行遍历
     50         for(int j=0;j<ve.size();j++)
     51         {
     52             int a=ve[j].x;
     53             int b=ve[j].y;
     54             //更新b之间的钱数
     55             if(dis[b]<dis[a]*ve[j].s)
     56             {
     57                 dis[b]=dis[a]*ve[j].s;
     58             }
     59         }
     60     }
     61     //在进行一次遍历,判断是否有正环
     62     for(int j=0;j<ve.size();j++)
     63     {
     64         int a=ve[j].x;
     65         int b=ve[j].y;
     66         //如果钱还能增加,则有正环
     67         if(dis[b]<dis[a]*ve[j].s)
     68         {
     69             return true;
     70         }
     71     }
     72     return false;
     73 }
     74 int main()
     75 {
     76     int n,ans=1;
     77     while(scanf("%d",&n)&&n!=0)
     78     {
     79         string str;
     80         for(int i=1;i<=n;i++)
     81         {
     82             cin>>str;
     83             //对钱的种类进行标记
     84             mp[str]=i;
     85         }
     86         int m;
     87         scanf("%d",&m);
     88         string a,b;
     89         node no;
     90         for(int i=0;i<m;i++)
     91         {
     92             cin>>a>>no.s>>b;
     93             no.x=mp[a];
     94             no.y=mp[b];
     95             //记录汇率
     96             ve.push_back(no);
     97         }
     98         printf("Case %d: ",ans++);
     99         //枚举所有的起点
    100         for(int i=1;i<=n;i++)
    101         {
    102             //判断有正环
    103             if(bellman(n,i))
    104             {
    105                 printf("Yes
    ");
    106                 break;
    107             }//如果到最后一个起点后海没有正环,则表示财富无法增加
    108             else if(i==n)
    109             {
    110                 printf("No
    ");
    111             }
    112         }
    113         //清除STL容器的内存
    114         ve.clear();
    115         mp.clear();
    116     }
    117 }
  • 相关阅读:
    结构本身和结构成员在内存中储存的形式
    C语言字符,字符串,字节操作常用函数
    可变参数列表
    用数组代替指针实现静态链表
    cout对象一些常用方法的总结
    cin对象的一些常用方法使用总结
    数据结构基本概念和术语总结
    GCH文件
    Ubuntu16 搭建Git 服务器
    Navicat 连接VMware中Ubuntu 下的mysql5.7遇到的坑
  • 原文地址:https://www.cnblogs.com/mzchuan/p/11468783.html
Copyright © 2020-2023  润新知