zoukankan      html  css  js  c++  java
  • 相辅相成的求最单源短路径算法:(SPFA& dijkstra)

    引用一位老oier的话:

    一道题如果边权没有负数,那么一定是在卡SPFA。这时候就用到了堆优化的Dijkstra;

    写在前面:
    多打代码!
    最好都掌握,灵活变通

    SPFA:

    主要用于稀疏图和有负权边的图上

    参考blog:
    https://blog.csdn.net/sxy201658506207/article/details/78779045

    (前向星版)
    注:起点为1

     1 #include <cstdio>
     2 #include <algorithm>
     3 
     4 #define MAXN 11111
     5 #define MAXM 222222
     6 #define INF 2000000000
     7 
     8 int n, m, cnt, ans;
     9 int q[MAXN];//队列 
    10 int p[MAXN];//用于打印解 
    11 int is_fuquan[MAXN];//用于判断负圈 
    12 
    13 int dis[MAXN], vis[MAXN]/*判断在不在队列中*/, head[MAXN];
    14 
    15 struct edge {
    16     int  y, val, next;
    17 }e[MAXM];//前向星 
    18 
    19 void add_edge(int x, int y, int val) {
    20     cnt++;//边的个数加一 
    21     e[cnt].y = y;
    22     e[cnt].val = val;
    23     e[cnt].next = head[x]; //当前边上一条同起点的边的编号为head[x] 
    24     head[x] = cnt; //当前边为最后一条以x为起点的边的编号 
    25     return ;
    26 }
    27 
    28 int main() {
    29     scanf("%d%d", &n, &m);
    30     for(int i = 1; i <= m; i++) {
    31         int x, y, z; 
    32         scanf("%d%d%d", &x, &y, &z);
    33         add_edge(x, y, z);//**双向边** 
    34         add_edge(y, x, z);
    35     }
    36 
    37     for(int i = 1; i <= n; i++) dis[i] = INF; //初始化 
    38     int l = 1, r = 1;
    39     q[1] = 1; //起点入队 
    40     dis[1] = 0;  
    41     vis[1] = 1; 
    42     while(l <= r) {
    43         int now = q[l];
    44         l++;//更新队首 
    45         vis[now] = 0; //注意不在队列中 要标记为0即 **重复入队**
    46         for(int i = head[now]; i; i = e[i].next) { //遍历所有now的出边 (重点)
    47             int y = e[i].y, val = e[i].val; 
    48             if(dis[y] > dis[now] + val) { //看now所有出边的终点 是否 需要更新
    49                 dis[y] = dis[now] + val; //更新长度 
    50                 p[y] = i;//记录边
    51                 if(vis[y] == 0) { //若终边不在队中 
    52                     q[++r] = y;
    53                     vis[y] = 1; //**y入队  标记为1** 
    54                     if(++is_fuquan[y] > n) return 0;//这可以是其他题目中需要返回的值
    55                      //这个就是用于发现负圈时及时退出(希望写在应该bool 值的SPFA函数中,然后返回false
    56                      //因为 一个点在没有负环的情况下,最多只会有 n-1 个点与它相连
    57                      //若有负环,这个便会一直for 所以这样做 
    58                 }
    59             } 
    60         }
    61     }
    62     for(int i = 1; i <= n; i++)
    63         printf("%d ", dis[i]);
    64     return 0;
    65 }

    /*
    4 6
    1 2 2
    2 3 2
    2 4 1
    1 3 5
    3 4 3
    1 4 4

    dijkstra:

    可以解不带负权边的图,在稠密图上有很好性能
    参考blog:
    ps:出自oier : https://www.cnblogs.com/jason2003/p/7222182.html
    过程:
    先建立一个dis数组,dis[i]表示第i号点到源点(1号点)的估计值, 然后我们在建立一个临界矩阵,叫做:map,map[i][j]=v表示从i到j这条边的权值是v, dis初始值除了源点本身都是无穷大。源点本身都是0. 我们用minn记录距离1号点最短的路径,留着以后会用。

    开始第二次更新时: 以minn相当于把2当源点,求所有点到它的最短路,加上它到真正的源点(1号点)的距离,就是我们要求的最短路。

    注:时时更新minn的值

    代码:用邻接矩阵实现dijksra:

    #include <iostream>
    #include <algorithm>
    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    using namespace std;
    int map[110][110];//这就是map数组,存储图
    int dis[10010];//dis数组,存储估计值
    int vis[10010];//vis[i]代表这个点有没有被当做源点去搜索过,1为有,0为没有。这样就不会重复搜索了。
    int n,m;
    void dijkstra(int u)//主函数,参数是源点编号
    {
        memset(dis,88,sizeof(dis));//把dis数组附最大值(88不是十进制的88,其实很大)
        int start=u;//先从源点搜索
        book[start]=1;//标记源点已经搜索过
        for(int i=1;i<=n;i++)
        {
            dis[i]=min(dis[i],map[start][i]);//先更新一遍
        }
        for(int i=1;i<=n-1;i++)
        {
            int minn=9999999;//这就是刚才所说的minn
            for(int j=1;j<=n;j++) {
            	if(vis[j]==0 && minn>dis[j])
                {
                    minn=dis[j];
                    start=j;
                }
    		}//找到离源点最近的点,然后把编号记录下来,用于搜索。
            	
            vis[start]=1;        
            for(int j=1;j<=n;j++)
                dis[j]=min(dis[j],dis[start]+map[start][j]);//以新的点来更新dis。
        }
    }
    int main()
    {
        cin>>n>>m;
        memset(map,88,sizeof(map));
        for(int i=1;i<=m;i++)
        {
            int a,b,c;
            cin>>a>>b>>c;
            map[a][b]=c;
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(i==j)
                    map[i][j]=0;
        dijkstra(1);//以1为源点。
        for(int i=1;i<=n;i++)
            cout<<dis[i]<<" ";
    }
    

    优化dijkstra:(堆)

    朴素的这玩意貌似很慢大概O(n方),堆优化应该只是取dis最小快了…吧
    出自我自己
    // Luogu P4779

    在这里插入图片描述

     1 #include <cstdio>
     2 #include <algorithm>
     3 #include <queue>
     4 using namespace std;
     5 #define MAXN 120000
     6 #define MAXM 233333
     7 #define INF 1000003647
     8 
     9 using namespace std;
    10 
    11 int n, m, S, cnt;
    12 int head[MAXN], dis[MAXN], vis[MAXN];
    13 
    14 struct node {
    15     int id, dis; //id表示点的编号  dis为在加入优先队列时的dis[id] 
    16     node(int id = 0, int dis = 0) : id(id), dis(dis) {} //构造函数 
    17     bool operator < (const node &x) const { //重载运算符                 //只有堆是这样的 
    18         return dis > x.dis; //小根堆  注意  这里用>时  最后堆顶为dis最小的  和sort的comp相反 
    19     }
    20 };
    21 
    22 priority_queue <node> q; //优先队列   堆 
    23 
    24 struct edge { 
    25     int y, val, next;
    26 }e[MAXM];
    27 
    28 void add_edge(int x, int y, int val) { //链式前向星 加边 
    29     e[++cnt].y = y;
    30     e[cnt].val = val;
    31     e[cnt].next = head[x];
    32     head[x] = cnt;
    33     return ;
    34 }
    35 //总是取最小的,无论它有没有被选 
    36 //注:最开始初始化正无穷!!
    37 //先放起点,并标记为已确定,再堆非空while(直到找完所有点的最短路) 
    38 //取出堆顶,先看它是不是确定的,若不是,就直接确定下来 ,然后遍历它的出边,并更新终点dis值(需比较),再放入堆 
    39 void dijkstra() {  
    40     for(int i = 1; i <= n; i++) dis[i] = INF;//初始化 
    41     q.push(node(S, 0)); //把起点加入 //加入编号&dis值 
    42     dis[S] = 0; // 初始化 
    43     while(!q.empty()) {
    44         node tmp = q.top();//当前所有元素中dis最小的 //取出来的是个node 
    45         q.pop(); //删除堆顶元素 
    46         int now = tmp.id; //当前点编号 
    47         if(vis[now]) continue; //若now为已确定元素(已知最短路的点) 则不执行下面语句 
    48         vis[now] = 1; //标记为已确定 //因为是堆 所以直接是最短的 //所以没有必要再找一遍 
    49         for(int i = head[now]; i; i = e[i].next) { //遍历边 
    50             int y = e[i].y; 
    51             if(dis[y] > tmp.dis + e[i].val) { //更新y的dis 
    52                 dis[y] = tmp.dis + e[i].val;//dis[now] 和 tmp.dis不一定是一样的哦,有可能它已经更新为一个更小的值了
    53                 q.push(node(y, dis[y])); //放入堆 
    54             }
    55         }
    56     }
    57     return ;
    58 }
    59 
    60 int main() {
    61     scanf("%d%d%d", &n, &m, &S);
    62     for(int i = 1, x, y, z; i <= m; i++) {
    63         scanf("%d%d%d", &x, &y, &z);
    64         add_edge(x, y, z);
    65     }
    66     dijkstra();
    67     for(int i = 1; i <= n; i++)
    68         printf("%d ", dis[i]);
    69 }

    需要注意的是:存在负权环路的图中不存在最短路(路径可以一直减少),SPFA算法可以用来判断图中是否有负权环路

    多源最短路出门下走至floyd

    自己选择的路,跪着也要走完。
  • 相关阅读:
    使用idea的过程中,遇到其中一个maven模块变成灰色
    Java进阶之类加载的完整过程(类的生命周期)(转载)
    mysql 一些小问题
    @select 添加判断
    om.alibaba.com.caucho.hessian.io.HessianProtocolException: com.alibaba.com.caucho.hessian.io.ObjectDeserializer: unexpected object java.lang.String (CU) 调用dubbo返回这个错误
    方法重载
    形参和实参
    方法
    数组操作两个常见的小问题
    数组初始化
  • 原文地址:https://www.cnblogs.com/tyner/p/10701709.html
Copyright © 2011-2022 走看看