最小生成树(prim & kruskal)

prim算法 O(n2)

普利姆算法适用于稠密图,思路与朴素版本的dijkstra很像,都是找到距离最近的点,然后用该点去更新邻边。

不同的是:

  1. dij找的是该点到源点的距离;而prim找的是该点到集合的最短距离,这里的集合指的是某些点所连成的连通块, 我们要找的最小生成树就是把这些所有的点都连接在一起,使得这些边的权值之和最小。
  2. 更新方式不同,由于更新方式的不同使得寻找最优解的方式也不同,在prim算法中就是一层一层的拓展更新,可能之前所连点的距离被后面的更新所取代,但dij就不一样了,dij每次确定一个点后就一直往前走,找到最小的边,每次用到源点距离最短的点更新邻边, dij更新的是 某点到源点的最短, prim更新的是某点到集合的最短距离,在确定某个点到源点 / 集合的最短距离后,要用该点去更新其邻边,dij这样更新d[j] = min(d[j], d[t] + g[t][j]); prim这样更新:d[j] = min(d[j], g[t][j]); 两者的g[t][j]都表示的是该点与邻点边权的大小.
  3. 求最小生成树要用一个变量加权统计,那么什么时候加?
    在确定某个点到集合最短距离之前,如果该确定点到集合的距离不是INF, 就加上,如果是INF说明所找到的与集合距离最近的点距离为INF,该点不连通,最小生成树肯定就不存在

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510;
int g[N][N], d[N];
bool st[N];
int n, m;
int res;

bool prim()
{
    memset(d, 0x3f, sizeof d);
    d[1] = 0;
        
    for(int i = 0; i < n; i ++ )
    {
        int t = -1;
        for(int j = 1; j <= n; j ++ )
            if(!st[j] && (t == -1 || d[t] > d[j]))
                t = j;
            
        if(d[t] == 0x3f3f3f3f)  return false;
        res += d[t];
        st[t] = true;
        
        for(int j = 1; j <= n; j ++ )
            d[j] = min(d[j], g[t][j]);
    }
    return true;
}

int main()
{
    cin >> n >> m;
    memset(g, 0x3f, sizeof g);
    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    
    if(prim())  cout << res << endl;
    else puts("impossible");
    return 0;
}

kruskal算法O(m)

kruskal算法用于求解稀疏图的最小生成树

思路:

  1. 将所有的边读入结构体中,按边权排序
  2. 初始化并查集数组p[],用于查看两个点是否在一个集合中
  3. 枚举所有的边,每次拿出最小的边,查询两个边的两个端点是否在一个集合中,如果不在一个集合中就加入到一个集合中(加边)同时记录res加边权,也要记录一下枚举完所有的边一共加了多少边。如果加边数小于n-1,说明可能存在自环或不连通,最小生成树不存在

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int n, m;
int res, cnt;   //cnt为边数
int p[N];

struct Edge{
    int a, b, w;
    bool operator < (const Edge &W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)
{
    if(x != p[x])   p[x] = find(p[x]);
    return p[x];
}

bool kruskal()
{
    for(int i = 0; i < m; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        a = find(a), b = find(b);
        if(a != b)
        {
            p[a] = b;
            res += w;
            cnt ++;
        }
    }
    if(cnt < n - 1) return false;
    return true;
}

int main()
{
    cin >> n >> m;
    
    for(int i = 1; i <= n; i ++ )   p[i] = i;
    
    
    for(int i = 0; i < m; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        edges[i] = {a, b ,c};
    }
    
    sort(edges, edges + m);
    
    if(kruskal())   cout << res << endl;
    else    puts("impossible");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值