最小生成树模板 prim算法和kurskal算法

63 阅读4分钟

什么是最小生成树?

prim和kurskal都是用来求最小生成树的,所以我们需要先了解什么是最小生成树。

一句话定义:最小生成树就是求遍历图中所有点的边权累加和最小。 它和最短路问题有点像。但是最短路问题并不一定遍历到所有的点,但是最小生成树会。

例如下图:

image.png

首先我们需要给所有的边排个序(因为要求最小边权累加和)

然后我们就开始连边: 图论必备算法之一:最小生成树 (易懂至极)_最小生成树算法 只能用于无向图吗?-CSDN博客

为什么要用并查集算法。

并查集算法用来判断两个点是否联通(直接联通或间接连通),如果没有联通那么就联通,并将边权值进行累加。如果已经联通(间接联通),那就没必要再联通(直接联通),这样就可以少累加一个边权值。

858. Prim算法求最小生成树

样例图:

image.png

我们初始的时候把所有点到集合的距离初始化为正无穷:

image.png

我们先从随便挑一个点,假设我们选节点1: image.png

我们通过节点1来更新其他点到集合的距离。

所谓其他点到集合的距离就是说其他点到集合的较短路径。

2到集合中点的距离是1,3到集合中点的距离是2,4到集合中点的距离是3:

image.png

然后我们在剩下的节点中1找到一个较小的节点,通过这个节点去更新其他点到集合中点的距离。

我们发现节点2的权值较小,所以我们选中节点2,此时去更新其他节点到集合中的点的距离。

节点3的最小路径是3,不用更新,节点2的最小路径是1,也不用更新。此时把节点2加入到集合当中:

image.png

我们从剩下的节点找权值较小的,节点3的权值比就节点4小,选中节点3,更新其他点到集合中点的距离。节点4的权值是3,已经是最小的了,不用更新,把节点3加入到集合当中。 image.png

只剩节点4了,把节点4也加入到集合当中:

image.png

#include<bits/stdc++.h>
using namespace std;
const int N = 510,INF=0x3f3f3f3f;
int dist[N];
int g[N][N];
bool st[N];
int n, m;


int prime()
{
	memset(dist,0x3f,sizeof dist);  //第一步先把所有距离初始化为正无穷

   //第二步n次迭代
	int res = 0;  //存最小生成树的所有边的长度之和
	for (int i = 0; i < n; i++)
	{
	    //每次找当前集合i的距离最小的点
		int t = -1;    //-1表示当前没有找到任何一点
		for (int j = 1; j <= n; j++)
			if (!st[j] && (t == -1 || dist[t] > dist[j]))  //如果当前边不确定是最短边 并且 没有找到任何一个点 或者t的距离大于j的距离
				t = j;                                     //t的距离更新为j的距离
		
		if (i && dist[t] == INF)return INF;                //如果不是第一个1点 并且 当前边仍然是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, 0x3f, sizeof g);
    while (m--)
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    int t = prime();

    if (t == 0x3f3f3f3f) puts("impossible");
    else cout << t;

    return 0;
}

image.png

859. Kruskal算法求最小生成树

第一步,首先我们给所有的边都排个序。

第二步,枚举所有的边,判断a,b之间是否连通,如果不连通,那么就给a,b之间连上一条边(加上权值c)。

#include<bits/stdc++.h>
using namespace  std;
const int N=2e5+10;
int n,m,res,cnt;
int p[N];

int find(int x)
{
    if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
struct Edge
{
    int a,b,w;
    bool operator<(const Edge &W)const
    {
        return w<W.w;
    }
}edges[N];


int main()
{
    //cin.tie(nullptr)->sync_with_stdio(false);
    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;
    
    
    //从小到大枚举所有边
    for(int i=0;i<m;i++)
    {
        int a=edges[i].a,b=edges[i].b,w=edges[i].w;
        
        //判断一下a,b是否连通
        a=find(a),b=find(b);
        if(a!=b)
        {
            p[a]=b;  //把a,b所在的集合合并
            res+=w;  //存最小生成树中所有树边的权重之和
            cnt++;   //统计一下加了多少条边
        }
    }
    
    if(cnt<n-1)puts("impossible");  //如果小于n-1,说明边没加够,当前子集不连通
    else cout<<res<<endl;
    return 0;
}

image.png