[总结]单源最短路(朴素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;
}

原文地址:https://www.cnblogs.com/Knight02/p/15799052.html