zoukankan      html  css  js  c++  java
  • 浅谈最短路

      最短路问题(short-path problem)是网络理论解决的典型问题之一,可用来解决管路铺设、线路安装、厂区布局和设备更新等实际问题。基本内容是:若网络中的每条边都有一个数值(长度、成本、时间等),则找出两节点(通常是源节点和阱节点)之间总权和最小的路径就是最短路问题。(摘自百度百科)

      常见的最短路算法有floyd,dijkstra和spfa。

    1.floyd

      floyd可以说是最暴力的最短路求法了,但其实floyd是用的动态规划的思想,只不过floyd可以求出任意2点之间的最短路,所以时间复杂度O(n^3)。

     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 using namespace std;
     5 int a[110][110];
     6 int x,y,v;
     7 int main()
     8 {
     9     int n,m;//n个点,m条边
    10     memset(a,127/3,sizeof(a));
    11     for(int i=1;i<=m;++i)
    12     {
    13         scanf("%d%d%d",&x,&y,&v);
    14         a[x][y]=a[y][x]=v;//双向边 
    15     }
    16     for(int k=1;k<=n;++k)
    17         for(int i=1;i<=n;++i) 
    18             for(int j=1;j<=n;++j) 
    19                 a[i][j]=min(a[i][k]+a[k][j],a[i][j]);//松弛操作
    20     printf("%d",a[1][n]);
    21     return 0; 
    22 }
    View Code

      例题:https://www.luogu.org/problemnew/show/P2888

      多组询问,n<=300,裸的floyd,但是这题稍微变一下形,因为不是求路径长度,而是求路上最高的跨栏的高度,只需要把floyd的加和变成取max就好了。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 using namespace std;
     5 int n,m,t;
     6 int a[310][310];
     7 int tmp1,tmp2,tmp3;
     8 int read()
     9 {
    10     int x=0,f=1;
    11     char ch=getchar();
    12     while(!isdigit(ch))
    13     {
    14         if(ch=='-')f=0;ch=getchar();
    15     }
    16     while(isdigit(ch))
    17     {
    18         x=(x<<1)+(x<<3)+ch-'0';ch=getchar();
    19     }
    20     return f?x:-x;
    21 
    22 }
    23 int main()
    24 {
    25     memset(a,127/3,sizeof(a));
    26     n=read(),m=read(),t=read();
    27     for(int i=1;i<=m;++i)
    28     {
    29         tmp1=read(),tmp2=read(),tmp3=read();
    30         a[tmp1][tmp2]=tmp3;
    31     }
    32     for(int k=1;k<=n;++k)
    33         for(int i=1;i<=n;++i)
    34             for(int j=1;j<=n;++j)
    35                 if(max(a[i][k],a[k][j])<a[i][j])a[i][j]=max(a[i][k],a[k][j]);
    36     for(int i=1;i<=t;++i)
    37     {
    38         tmp1=read(),tmp2=read();
    39         if(a[tmp1][tmp2]<214748364)cout<<a[tmp1][tmp2]<<endl;
    40         else cout<<-1<<endl;
    41     }
    42     return 0;
    43 }
    View Code

      floyd还可以用来求图的连通性,判断2个点是否联通

    1 for(int k=1;k<=n;++k)
    2     for(int i=1;i<=n;++i)
    3         for(int j=1;j<=n;++j)
    4             f[i][j]|=(f[i][k]&f[j][k])//如果i j本身联通,就是联通了,如果i k联通,k j联通,那么i j也联通 
    View Code

    2.dijkstra

      dijkstra解决的是单源最短路问题,时间复杂度O(n^2),dijkstra是贪心的思想,不能解决存在负边权的图。

      dijkstra算法流程:从起点开始,更新每一个可以被更新的点,然后找出来一个距起点最近的点,用它更新所有可以被更新的点,依次循环n次。在这里可以被更新的点指的是所有用当前点松弛可以得到与起点更近的距离的点。

      算法证明(窝自己瞎yy的,不喜勿喷):

      最开始的时候找到的都是和起点联通的点,那么这里面的距离起点最近的点一定不可能在被其他点更新了,也就是它和起点的最短路就是它和起点连接的这条边了。有一个显而易见的性质就是,一个点的距离起点的最短路上的点到起点都是最短路,因为不是这样的话就会存在长度更小的路径。所以此时用第二个点更新出来的距离起点最小的点的最短路径长度也出来了,在用它去更新别人,依次更新n次,终点的最短路也就找到了。

      我是很少用dijkstra的,因为基本上可以用spfa过。没找到例题,也不知道代码对不对就先不放了。

    3.spfa

      spfa应该是大部分oier最熟悉的最短路算法了,spfa之所以叫spfa是因为发明算法的人称之为 special fast algorithm,也就是非常快的意思啦,一般说spfa的复杂度是O(km)的,切k的一般为2,但实际证明并没有那么快,spfa会被稠密图卡。

      例题:https://www.luogu.org/problemnew/show/P3371#sub

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<vector>
     4 using namespace std;
     5 int n,m,s;
     6 vector<int>tu[10010];//cun dian
     7 vector<int>v[10010];//cun bian quan
     8 int from,to,val;
     9 int q[10010*4];
    10 int head=1,tail=1;
    11 bool vis[10010];
    12 int dis[10010];
    13 void spfa(){
    14     q[head]=s;
    15     vis[s]=1;
    16     while(head<=tail){
    17         int k=q[head];
    18         vis[k]=0;
    19         head++;
    20         for(int i=0;i<tu[k].size();++i)
    21           {
    22               if(dis[k]+v[k][i]<dis[tu[k][i]])
    23             {
    24               dis[tu[k][i]]=dis[k]+v[k][i];
    25                 if(!vis[tu[k][i]])vis[tu[k][i]]=1,q[++tail]=tu[k][i];
    26             }    
    27          }
    28       }
    29 }
    30 int main(){
    31     cin>>n>>m>>s;
    32     for(int i=1;i<=n;++i)dis[i]=2147483647;
    33     dis[s]=0;
    34     for(int i=1;i<=m;++i)
    35       {
    36           scanf("%d%d%d",&from,&to,&val);
    37           tu[from].push_back(to);
    38           v[from].push_back(val);
    39       }
    40     spfa();
    41     for(int i=1;i<=n;++i)     
    42        cout<<dis[i]<<" ";
    43     cout<<endl;
    44     return 0;
    45 } 
    View Code

      模板题。

      spfa还有一个应用是判负环,如果存在负边权回路,那么这个回路上的点会一直被更新,此时只要记录每一个点入队的次数,如果超过了n次,那么是一定存在负边权回路的。当然也有dfs的写法,每次如果可以更新就顺着这个点dfs下去,如果dfs到了一个已经dfs过的点,那么这个点属于一个环,并且是负环。

      例题:https://www.luogu.org/problemnew/show/P3385

      这题貌似会卡bfs版的spfa,所以放上dfs的代码好了

     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 using namespace std;
     5 const int maxn=200005;
     6 int t; 
     7 int n,m;
     8 struct zhw{
     9     int to,last,val;
    10 }tu[maxn<<1];
    11 int head[maxn],tot=0;
    12 int x,y,v;
    13 void add(int x,int y,int v)
    14 {
    15     tot++;
    16     tu[tot].last=head[x],head[x]=tot,tu[tot].to=y,tu[tot].val=v;
    17 }
    18 bool flag;
    19 bool vis[maxn];
    20 int dis[maxn];
    21 void dfs(int x)
    22 {
    23     vis[x]=1;
    24     for(int i=head[x];i;i=tu[i].last)
    25         if(dis[tu[i].to]>dis[x]+tu[i].val)
    26         {
    27             if(vis[tu[i].to]||flag)
    28             {
    29                 flag=1;break; 
    30             }
    31             dis[tu[i].to]=dis[x]+tu[i].val;dfs(tu[i].to);
    32         }
    33     vis[x]=0;
    34 }
    35 int main()
    36 {
    37     scanf("%d",&t);
    38     while(t--)
    39     {
    40         scanf("%d%d",&n,&m);
    41         memset(head,0,sizeof(head)),tot=0,flag=0;
    42         for(int i=1;i<=m;++i)
    43         {
    44             scanf("%d%d%d",&x,&y,&v);
    45             if(v>0)add(x,y,v),add(y,x,v);
    46             else
    47             add(x,y,v);
    48         }
    49         for(int i=1;i<=n;++i)
    50         {
    51             dfs(i);
    52             if(flag)break;
    53         }
    54         if(flag)printf("YE5
    ");
    55         else printf("N0
    ");
    56     }
    57 }
    View Code
    知世故而不世故,处江湖而远江湖,才是最成熟的善良
  • 相关阅读:
    media query 开发总结
    整屏滚动
    移动端reset样式
    中国天气网 城市代码 sql语句
    php文章tag标签的增删
    oracle的分号和斜杠/
    php 操作 oracle lob 数据2
    php 操作 oracle lob 数据
    oracle创建用户
    php进度条
  • 原文地址:https://www.cnblogs.com/yuelian/p/8743033.html
Copyright © 2011-2022 走看看