zoukankan      html  css  js  c++  java
  • 关于最短路径问题(图论)

    模版题为【hdu 2544】最短路。比较详细的解释请见:【转】彻底弄懂最短路径问题(图论)

    前言:我先写一些总结性的话——
    1.推荐使用优先队列优化后的Dijkstra算法,速度快又稳定,而SPFA算法虽快但不稳定;但也有特殊情况,譬如说:【uva 658】It's not a Bug, it's a Feature!(图论--Dijkstra或spfa算法+二进制表示+类“隐式图搜索”)这些类“隐式图搜索”的题。然而......似乎我们这边大家都喜欢用SPFA,因为最初接触的就是它而且它就是一个BFS,比较好打;
    2.出现负边和判断负环都用Bellman-Ford算法(也就是SPFA算法);

    3.Floyd算法本质是DP或贪心思想,枚举出了所有路径的情况,一些“合法性”“可到达性”的题目可以用它。

    1.Dijkstra算法

    概述:依次将当前没有标记过的,且与源点的距离最近的点标记 / 纳入联盟内,并拓展该点,更新与之相连边的所有另一点的离源点的距离。   P.S.之前我以为几乎就是MST中的Prim算法;这个原理我真心不怎么理解 _(눈_ 눈」∠)_
    实现:邻接表+优先队列。
    时间复杂度:O(n^2+m); O(n log n+m)。
    应用:有向图和无向图,正权图上的单源最短路(Single-Source Shortest Paths, SSSP,即从单个源点出发到所有结点的最短路)。
    注意——正权图上可能有零环和正环,但都不会影响最短路的计算;Dijkstra中优先队列的top()是汇点的值时就可以跳出,因为剩下的可以拓展的点都比它的值大,由于没有负权边,就不可能通过一些边又比它小了。

     1 #include<cstdio>
     2 #include<cstdlib>
     3 #include<cstring>
     4 #include<iostream>
     5 using namespace std;
     6 
     7 const int N=110,M=10010,D=1010,INF=(int)1e9;
     8 int n,m;
     9 int last[N],d[N],vis[N];
    10 struct edge{int x,y,d,next;}a[2*M];
    11 
    12 int mmin(int x,int y) {return x<y?x:y;}
    13 void ins(int len,int x,int y,int d)
    14 {
    15     a[len].x=x,a[len].y=y,a[len].d=d;
    16     a[len].next=last[x],last[x]=len;
    17 }
    18 int Dijkstra()
    19 {
    20     memset(vis,0,sizeof(vis));
    21     memset(d,63,sizeof(d));
    22     d[1]=0;
    23     for (int k=1;k<=n;k++)//加n个点进联盟
    24     {
    25       int p=0,mn=INF;
    26       for (int i=1;i<=n;i++)
    27         if (!vis[i] && d[i]<mn) p=i,mn=d[i];
    28       vis[p]=1;
    29       for (int i=last[p];i;i=a[i].next)
    30       {
    31         int y=a[i].y;
    32         d[y]=mmin(d[y],d[p]+a[i].d);
    33       }
    34     }
    35     return d[n];
    36 }
    37 int main()
    38 {
    39     while (1)
    40     {
    41       scanf("%d%d",&n,&m);
    42       if (!n && !m) break;
    43       memset(last,0,sizeof(last));
    44       for (int i=1;i<=m;i++)
    45       {
    46         int x,y,d;
    47         scanf("%d%d%d",&x,&y,&d);
    48         ins(2*i-1,x,y,d),ins(2*i,y,x,d);
    49       }
    50       printf("%d
    ",Dijkstra());
    51     }
    52     return 0;
    53 }
    54 
    55 Dijkstra
    Dijkstra
     1 #include<cstdio>
     2 #include<cstdlib>
     3 #include<cstring>
     4 #include<iostream>
     5 #include<queue>
     6 using namespace std;
     7 
     8 const int N=110,M=10010,D=1010,INF=(int)1e9;
     9 int n,m;
    10 int last[N],vis[N],d[N];//,path[N];
    11 struct edge{int x,y,d,next;}a[2*M];
    12 struct node
    13 {
    14     int x,d;
    15     bool operator < (const node& now) const
    16     { return d>now.d; }// > 使原来的优先队列排序反转,dmin first out
    17     node() {}
    18     node(int u,int v) {x=u;d=v;}
    19 };
    20 priority_queue<node> q;//默认从大到小排序
    21 
    22 int mmin(int x,int y) {return x<y?x:y;}
    23 void ins(int len,int x,int y,int d)
    24 {
    25     a[len].x=x,a[len].y=y,a[len].d=d;
    26     a[len].next=last[x],last[x]=len;
    27 }
    28 int Dijkstra()//!!!important!!
    29 {
    30     while (!q.empty()) q.pop();
    31     memset(vis,0,sizeof(vis));
    32     memset(d,63,sizeof(d));
    33     d[1]=0;
    34     q.push(node(1,0)); //这里无 vis[x]=1;
    35     while (!q.empty())
    36     {
    37       node now=q.top(); q.pop();
    38       int x=now.x,tmp=now.d;
    39       if (x==n) return d[x];//见“注意”
    40       if (vis[x]) continue;//或 if (tmp!=d[x]) continue; 表示标记过了,不再用它更新相关的点,以防止重复拓展
    41       vis[x]=1;
    42       for (int i=last[x];i;i=a[i].next)
    43       {
    44         int y=a[i].y;
    45         if (d[x]+a[i].d<d[y])
    46         {
    47           d[y]=d[x]+a[i].d;
    48           //path[y]=x;
    49           q.push(node(y,d[y]));//no judge
    50         }
    51       }
    52     }
    53     return -1;
    54 }
    55 int main()
    56 {
    57     while (1)
    58     {
    59       scanf("%d%d",&n,&m);
    60       if (!n && !m) break;
    61       memset(last,0,sizeof(last));
    62       for (int i=1;i<=m;i++)
    63       {
    64         int x,y,d;
    65         scanf("%d%d%d",&x,&y,&d);
    66         ins(2*i-1,x,y,d),ins(2*i,y,x,d);
    67       }
    68       printf("%d
    ",Dijkstra());
    69     }
    70     return 0;
    71 }
    72 
    73 Dijkstra+优先队列(important!!)
    Dijkstra+优先队列(important!!)

    附上一个小笑话 ヾ(o◕∀◕)ノヾ Dijkstra这么厉害,为什么没有把求多点之间的最短路的Floyd算法也给发明了。——原因是,它叫Dijkstra,不叫Dkijstra。··(≧∀≦)··

    2.Bellman-ford算法

    概述:由于最多只包含n-1个结点,便通过n-1轮,对m条边进行松弛,更新端点的值。
    实现:队列。

    时间复杂度:O(nm)。
    应用:有向图和无向图,正权和负权图的最短路,判断负环;若要算出单源最短路,需要让源点和每个点之间加一条权值为0的边,这样每个点都可以被访问。

     1 #include<cstdio>
     2 #include<cstdlib>
     3 #include<cstring>
     4 #include<iostream>
     5 #include<queue>
     6 using namespace std;
     7 
     8 const int N=110,M=10010,D=1010,INF=(int)1e9;
     9 int n,m;
    10 int last[N],vis[N],d[N];//,path[N];
    11 struct edge{int x,y,d,next;}a[2*M];
    12 
    13 int mmin(int x,int y) {return x<y?x:y;}
    14 void ins(int len,int x,int y,int d)
    15 {
    16     a[len].x=x,a[len].y=y,a[len].d=d;
    17     a[len].next=last[x],last[x]=len;
    18 }
    19 int Bellman_ford()//!!!important!!
    20 {
    21     memset(vis,0,sizeof(vis));
    22     memset(d,63,sizeof(d));
    23     d[1]=0;
    24     for (int k=1;k<n;k++)
    25       for (int i=1;i<=m;i++)
    26       {
    27         int x=a[i].x,y=a[i].y;
    28         d[y]=mmin(d[y],d[x]+a[i].d);
    29       }
    30     return d[n];
    31 }
    32 int main()
    33 {
    34     while (1)
    35     {
    36       scanf("%d%d",&n,&m);
    37       if (!n && !m) break;
    38       memset(last,0,sizeof(last));
    39       for (int i=1;i<=m;i++)
    40       {
    41         int x,y,d;
    42         scanf("%d%d%d",&x,&y,&d);
    43         ins(2*i-1,x,y,d),ins(2*i,y,x,d);
    44       }
    45       m=2*m;
    46       printf("%d
    ",Bellman_ford());
    47     }
    48     return 0;
    49 }
    Bellman-ford算法

    3.SPFA算法(Shortest Path Faster Algorithm)

    概述:如它的名字,它其实是在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作
    实现:队列。
    时间复杂度:O(km),k为每个节点进入队列的次数,且k一般<=2;最坏情况是O(nm),比如图为完全图时会有n-1条边的松弛路径,每个点会入队n-1次。{详见Dijkstra、Bellman_Ford、SPFA、Floyd算法复杂度比较。}
    应用:有向图和无向图,正权和负权图上的单源最短路,判断负环。拓展:若要算出单源最短路,需要让源点和每个点之间加一条权值为0的边,这样每个点都可以被访问。

     1 #include<cstdio>
     2 #include<cstdlib>
     3 #include<cstring>
     4 #include<iostream>
     5 #include<queue>
     6 using namespace std;
     7 
     8 const int N=110,M=10010,D=1010,INF=(int)1e9;
     9 int n,m;
    10 int last[N],inq[N],cnt[N],d[N];//,path[N];
    11 struct edge{int x,y,d,next;}a[2*M];
    12 queue<int> q;
    13 
    14 int mmin(int x,int y) {return x<y?x:y;}
    15 void ins(int len,int x,int y,int d)
    16 {
    17     a[len].x=x,a[len].y=y,a[len].d=d;
    18     a[len].next=last[x],last[x]=len;
    19 }
    20 int spfa()
    21 {
    22     while (!q.empty()) q.pop();
    23     memset(d,63,sizeof(d));
    24     memset(inq,0,sizeof(inq));
    25     memset(cnt,0,sizeof(cnt));
    26     q.push(1);
    27     inq[1]=cnt[1]=1,d[1]=0;
    28     while (!q.empty())
    29     {
    30       int x=q.front();
    31       q.pop(), inq[x]=0;
    32       for (int i=last[x];i;i=a[i].next)
    33       {
    34         int y=a[i].y;
    35         if (d[x]+a[i].d<d[y])
    36         {
    37           d[y]=d[x]+a[i].d;
    38           //path[y]=x; 或 path[y]=i;
    39           if (!inq[y])
    40           {
    41             inq[y]=1,cnt[y]++;
    42             if (cnt[y]>n) return -1;//> 判断负圈,一个点被更新了超过n次
    43             q.push(y);
    44           }
    45         }
    46       }
    47     }
    48     return d[n];
    49 }
    50 int main()
    51 {
    52     while (1)
    53     {
    54       scanf("%d%d",&n,&m);
    55       if (!n && !m) break;
    56       memset(last,0,sizeof(last));
    57       for (int i=1;i<=m;i++)
    58       {
    59         int x,y,d;
    60         scanf("%d%d%d",&x,&y,&d);
    61         ins(2*i-1,x,y,d),ins(2*i,y,x,d);
    62       }
    63       printf("%d
    ",spfa());
    64     }
    65     return 0;
    66 }
    Spfa(important!!)

    唉,其实用 bfs 的 spfa 判断负环很慢!应该用 dfs 的。具体解释和代码请见:【洛谷 P3385】模板-负环(图论--spfa)

    4.Floyd-warshall算法

    概述:枚举所有情况,限制每两点中间通过的结点范围而松弛n轮。
    实现:DP思想,递推。
    时间复杂度:O(n^3)。
    应用:有向图和无向图,正权和负权图上的每两点之间的最短路问题 和 连通性结果为有向图的传递闭包(Transitive Closure. 数学上定义为:在集合X上的二元关系R的传递闭包是包含R的X上的最小传递关系)问题。

     1 #include<cstdio>
     2 #include<cstdlib>
     3 #include<cstring>
     4 #include<iostream>
     5 #include<queue>
     6 using namespace std;
     7 
     8 const int N=110,M=10010,D=1010,INF=(int)1e9;
     9 int n,m;
    10 int d[N][N];
    11 
    12 int mmin(int x,int y) {return x<y?x:y;}
    13 int Floyd()
    14 {
    15     for (int k=1;k<=n;k++)
    16      for (int i=1;i<=n;i++)
    17       for (int j=1;j<=n;j++)//此时的d[i][j]表示i,j间只能经过1~k点时的最短路径
    18         d[i][j]=mmin(d[i][j],d[i][k]+d[k][j]);//原3维方程:d[k][i][j]=mmin(d[k][i][j],d[k-1][i][k]+d[k-1][k][j]);
    19     return d[1][n];
    20 }
    21 int main()
    22 {
    23     while (1)
    24     {
    25       scanf("%d%d",&n,&m);
    26       if (!n && !m) break;
    27       memset(d,63,sizeof(d));
    28       for (int i=1;i<=m;i++)
    29       {
    30         int x,y,t;
    31         scanf("%d%d%d",&x,&y,&t);
    32         d[x][y]=d[y][x]=t;
    33       }
    34       printf("%d
    ",Floyd());
    35     }
    36     return 0;
    37 }
    Floyd算法
  • 相关阅读:
    克如斯卡尔 P1546
    真正的spfa
    第四课 最小生成树 要点
    关于vscode中nullptr未定义
    cmake学习笔记
    python学习笔记
    (BFS 图的遍历) 2906. kotori和迷宫
    (图论基础题) leetcode 997. Find the Town Judge
    (BFS DFS 并查集) leetcode 547. Friend Circles
    (BFS DFS 图的遍历) leetcode 841. Keys and Rooms
  • 原文地址:https://www.cnblogs.com/konjak/p/6031912.html
Copyright © 2011-2022 走看看