最小生成树典型算法总结~

最小生成树的分类

🐖:最小生成树一般用于无向图,有向图几乎不会用到。
在这里插入图片描述
方法选择:
稠密图:用朴素版Prim算法
稀疏图:用Kruskal算法
堆优化版Prim一般不常用!

朴素版Prim

大体思路可以参考Dijkstra算法,并注意相同点和不同点
思路
1.将所有距离都初始化为+ ∞
dist[1] = 0,dist[i] = + ∞ (dist数组表示起点到i点的距离)
2.n次迭代
for(i = 0; i < n; i ++ )
①找到不在集合当中的距离最近的点(s表示当前已经在连通块中的点),赋给t
②用t来更新其他的点到集合的距离(注意和dijkstra算法的区别)
③把t加到集合中去 st[t] = true
注:到集合的距离:连向集合内部的点中长度最小的边
典例——AcWing 858. Prim算法求最小生成树
在这里插入图片描述

#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,0x3f,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[t] > dist[j])) //找到不在集合当中的距离最近的点
                t = j;

		
		if(i && dist[t] == INF) return INF; //不是第一个点且不连通,不存在最小生成树
        if(i) res += dist[t]; 


        
        for(int j = 1; j <= n; j ++ ) //用t更新点到集合的距离
            dist[j] = min(dist[j],g[t][j]); 
            
        st[t] = true; //将t加到集合中去
    }

    return res;
}

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(c,g[a][b]);
    }
    
    int t = prim();
    
    if(t == INF) puts("impossible");
    else 
    cout << t << endl;
    
    return 0;
}

注:

 if(i) res += dist[t]; 
      for(int j = 1; j <= n; j ++ ) //用t更新点到集合的距离
            dist[j] = min(dist[j],g[t][j]); 

上面这两步顺序不能颠倒!

一定先把结果加进去,再用t点更新其他边。原因是可能有自环存在,如果先用t更新,可能会把t点的自环更新到dist中,而生成树中不能有自环存在。这样再加到结果中就是不对的。
*堆优化版Prim算法,思路和堆优化版Dijkstra算法基本一致,而且很麻烦,基本不会用。想了解堆优化的思路可以参考我之前的这篇博:最短路问题

Kruskal算法

思路:
1)先将所有边按权重从小到大排序 O(mlogm) (算法瓶颈,这一步是最慢的一步,但是这只是理论的时间复杂度,实际上Kruskal算法中,这一步的常数非常小,表现的效果也通常很好)

2)从小到大枚举每条边 a,b 权重 c

if(a,b不连通)

将这条边加入集合中

注: 第2步要用到并查集,可以看我以前的博客并查集,参考题目“连通块中点的数目“
优点:

1)不需要用邻接表等复杂的数据结构存边,开一个结构体就行了;

2)思路简单

典例 AcWing 859. Kruskal算法求最小生成树
在这里插入图片描述

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 100010;

int n,m;
int p[N];

//用结构体来存储所有边
struct Edge
{
    int a,b,w; 
    
    //重载小于号,方便排序(按权重来排序)
    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[N];

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

int main()
{
    cin >> n >> m;
    
    for(int i = 0; i < m; i ++ )
    {
        int a,b,w;
        cin >> a >> b >> w;
        edges[i] = {a,b,w};
    }
    
    sort(edges,edges + m);
    
    for(int i = 1; i <= n; i ++ ) p[i] = i; //初始化并查集
    
    int res = 0, cnt = 0; //res:存最小生成树中所有树边的权重之和; 
    //cnt:存当前边的数目(即当前加入多少条边)
    
    //从小到大枚举所有边
    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) //判断a,b是否连通
        {
            p[a] = b; //将两个集合合并(即将这条边加入到集合中)
            res += w;
            cnt ++;
        }
    }
    
    if(cnt < n - 1) puts("impossible"); //不连通
    else cout << res << endl;  //所有树边的长度之和
    
    return 0;
}

本篇主要讲了Prim和Kruskal两个最小生成树的算法,欢迎大家批评指正!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值