zoukankan      html  css  js  c++  java
  • [总结]单源最短路(朴素Dijkstra)与最小生成树(Prim,Kruskal)

    目录

    最短路

    朴素Dijkstra

    最小生成树

    Prim 算法

    Kruskal 算法




    最短路

    朴素Dijkstra

    时间复杂度:        O(n2+m) , n 表示点数,m 表示边数

    稠密图           存储形式:邻接矩阵

    模板: g[x][y]存的是初始化时边权, st[i]表示i是否加入正在逐步扩张的集合, dist[i] 存的是1到i的最短距离,也就是我们的目标

    思路:

    求1 → n 的最短路径

    1. 初始化距离 dist[1] = 0,  dist[i] = +∞

    2. for  i   1~n:

            for  j  1~n:

                    t  ← 不在集合s中的, 且距离集合最近的点

            s  ← t  (加入组织,即st[t]=true)

            for  j  1~n:

                    用 t 来更新其他点的的距离(已在集合中的点到1的距离可能不是最短,dist[j]取值更新为 min(dist[j], dist[t] + g[t][j]) )

    849. Dijkstra求最短路 I - AcWing题库高质量的算法题库https://www.acwing.com/problem/content/851/

    输入样例:

    3 3
    1 2 2
    2 3 1
    1 3 4
    

    输出样例:

    3

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N = 510;
    int n, m;
    int dist[N],g[N][N];
    bool st[N]; //标记是否在集合中  
    
    int dijkstra()
    {
        memset(dist, 0x3f, sizeof dist);
        dist[1] = 0;
        
        for(int i = 0; i < n; i ++)
        {
            int t = -1;
            for(int j = 1; j <= n; j ++)
                if(!st[j] && (t==-1 || dist[j] < dist[t]))
                    t = j;
                    
            //if(t == n) break; //可以提前break, 优化时间开销 
            
            st[t] = true;
            for(int j = 1; j <= n; j ++)    //更新集合中的最短路径
                dist[j] = min(dist[j], dist[t]+g[t][j]);
            
        }
        
        if(dist[n] == 0x3f3f3f3f) return -1;
        else return dist[n];
    }
    int main()
    {
        cin >> n >> m;
        
        memset(g, 0x3f, sizeof g);
    
        int a,b,c;
        for(int i = 0; i < m; i++)
        {
            scanf("%d%d%d", &a, &b, &c);//边数较多,scanf省时
            g[a][b] = min(c, g[a][b]);  //由题意,图中可能存在重边和自环
        }
        
        cout << dijkstra();
    }

    最小生成树

    Prim 算法

    (与Dijkstra思想非常相似,只有更新dist时有区别,前者是到集合中与之距离最小的点,后者是到起始点的距离最小的点)

    思路:

    1. 初始化距离  dist[i] = +∞

    2. for  i   1~n:

            for  j  1~n:

                    t  ← 不在集合s中的, 且距离集合最近的点

            若i!=1(即不是初始时刻的点)且dist[t]==+∞(该图不连通), 直接退出(补充:只要不是第一个点,dist[t]就表示当前的点和已经连好的的集合里的某点之间的长度)

            for  j  1~n:

                    用 t 来更新其他点的的距离(已在集合中的点到1的距离可能不是最短,dist[j]取值更新为 min(dist[j],  g[t][j]) ) (补充:min(dist[j], g[t][j])中dist[j]表示之前当点t没有加入集合时 点j到集合的距离, g[t][j] 表示如果点t加入集合时 j到集合的距离)

    输入样例:

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

    输出样例:

    6

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N = 510, INF = 0x3f3f3f3f;
    int n,m;
    int g[N][N],dist[N];
    bool st[N];
    
    int prim()
    {
        memset(dist, INF, sizeof dist);
        int res = 0;    //最小生成树的权值之和
        for(int i = 0; i < n; i++)
        {
            int t = -1;
            for(int j = 1; j <= n; j++)
                if(!st[j] && (t == -1 || dist[j] < dist[t]))
                    t = j;
                    
            if(i && dist[t]==INF) return INF;
            if(i) res += dist[t];//★,在更新之前加, 否则可能有自环的权会被算进来
            
            for(int j = 1; j <= n; j++)
                dist[j] = min(dist[j], g[t][j]);//★
            
            st[t] = true;//★
        }
        return res;
    }
    
    int main()
    {
        cin >> n >> m;
        
        memset(g, INF, sizeof g);
        
        int a, b, c;
        while (m -- )
        {
            scanf("%d%d%d", &a, &b, &c);
            g[a][b] = g[b][a] = min(c, g[a][b]); //★
        }
        
        int res = prim();
        if(res == INF) cout << "impossible";
        else cout << res;
        return 0;
    }

    Kruskal 算法

    (本质其实是并查集)

    思路:

    1. 所有边按权重从小到大排序

    2. 枚举每条边ab, 权重c

            如果a,b不连通,将此边加入集合

    输入样例:

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

    输出样例:

    6

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N = 2e5+10, INF = 0x3f3f3f3f;
    int n ,m;
    int p[N];
    
    struct Edge{
        int a, b, w;
        
        bool operator < (const Edge& e)const
        {
            return w < e.w;
        }
    }e[N];
    
    int find(int x)    //并查集(压缩路径)
    {
        if(p[x]!=x) p[x] = find(p[x]);
        return p[x];
    }
    
    int kruskal()
    {
        sort(e, e+m);
        if(e[0].w == INF) return INF;
        
        for (int i = 0; i < n; i ++ ) p[i] = i;    //并查集初始化
        
        int res = 0, cntEdge = 0;
        
        for (int i = 0; i < m; i ++ )
        {
            int pa = find(e[i].a), pb = find(e[i].b);
            if(pa != pb){
                res += e[i].w;
                p[pa] = pb;
                cntEdge ++;
            }
        }
        
        if(cntEdge != n-1) return INF;
        else return res;
    }
    int main()
    {
        cin >> n >> m;
        
        int a, b, w;
        
        for (int i = 0; i < m; i ++ )
        {
            scanf("%d%d%d", &a, &b, &w);
            e[i] = {a, b, w};
        }
        
        int res = kruskal();
        
        if(res == INF) cout << "impossible";
        else cout << res;
        return 0;
    }

  • 相关阅读:
    Codeforces 845E Fire in the City 线段树
    Codeforces 542D Superhero's Job dp (看题解)
    Codeforces 797F Mice and Holes dp
    Codeforces 408D Parcels dp (看题解)
    Codeforces 464D World of Darkraft
    Codeforces 215E Periodical Numbers 容斥原理
    Codeforces 285E Positions in Permutations dp + 容斥原理
    Codeforces 875E Delivery Club dp
    Codeforces 888F Connecting Vertices 区间dp (看题解)
    Codeforces 946F Fibonacci String Subsequences dp (看题解)
  • 原文地址:https://www.cnblogs.com/Knight02/p/15799052.html
Copyright © 2011-2022 走看看