【图论】最小生成树(Prim算法/Kruskal算法)

什么是最小生成树?
在这里插入图片描述

Prim算法
就是先从任意一点开始,接下来找与其距离最小的选进去。每次选择已有点最接近距离的点并且标记。
时间复杂度O(n^2)

#include<bits/stdc++.h>
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int maxn= 5050;
const int inf = 0x3f3f3f3f;
typedef long long ll;
ll g[maxn][maxn];
ll mst[maxn];
ll lowcost[maxn];
ll n,m;
int prim(int x)  //从x点开始扩展
{
    int sum=0;             //边权和
    for(int i=1;i<=n;i++)
    {
		lowcost[i]=g[x][i];   //lowcost[i]表示以i为终点的边中最小的权值,等于-1 表示已在集合U中
		//mst[i]=x;           //记录路径的话 开个mst数组 mst[i]=x;表示当前集合U中到点的距离最小的点为x 即边(x,i)为候选边
    }
    lowcost[x]=-1;            
    for(int i=1;i<=n-1;i++)
    {
        int mind=inf,minid=0;
        for(int j=1;j<=n;j++)
        {
            if(lowcost[j]<mind&&lowcost[j]!=-1)
            {
                mind=lowcost[j];//选出最小值(要加入最小生成树的边的边权)
                minid=j;//记录要加入的点
            }
        }
        sum+=mind;      
        lowcost[minid]=-1;
        for(int i=1;i<=n;i++)
        {
            if(lowcost[i]>g[minid][i])           //更新候选值
            {
                lowcost[i]=g[minid][i];
            }
        }
    }
    return sum;                  //返回最小生成树边权值和
}
int main()
{
		IO;
   		scanf("%d %d",&n,&m);   //n个点,m条边
        for(int i=1; i<=n; i++)     //赋初值
        {
            for(int j=1; j<=n; j++)
                g[i][j]=inf;
        }
        int u,v,w;
        for(int i=0;i<m;i++)
        {
            scanf("%d %d %d",&u,&v,&w);
        	if(w<=g[u][v]) g[u][v]=g[v][u]=w;//去重边
        }
        int sum=prim(1);
        if(sum<inf) printf("%d\n",sum);
		else cout<<"orz"<<endl;
}

Kruskal算法
前置技能:并查集算法

#include<bits/stdc++.h>
using namespace std;
struct Edge
{
    int u,v,w;
}edge[200005];

int fa[5005],n,m,ans,eu,ev,cnt;

inline bool cmp(Edge a,Edge b)
{
    return a.w<b.w;
}
inline int find(int x)
{
    while(x!=fa[x]) x=fa[x]=fa[fa[x]];
    return x;
}

inline void kruskal()
{
    sort(edge,edge+m,cmp);
    for(int i=0;i<m;i++)
    {
        eu=find(edge[i].u), ev=find(edge[i].v);
        if(eu==ev)
        {
            continue;
        }
        ans+=edge[i].w;
        fa[ev]=eu;
        if(++cnt==n-1)
        {
            break;
        }
    }
}
int main()
{
	cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        fa[i]=i;
    }
    for(int i=0;i<m;i++)
    {
        cin>>edge[i].u;
		cin>>edge[i].v;
		cin>>edge[i].w;
    }
    kruskal();
    printf("%d",ans);
    return 0;
}

Prim和Kruskal的区别:
Prim算法是依赖于点的算法。它的基本原理是从当前点寻找一个离自己(集合)最近的点然后把这个点拉到自己家来(距离设为0),同时输出一条边,并且刷新到其他点的路径长度。俗称,刷表。
根据Prim算法的特性可以得知,它很适合于点密集的图。通常在教材中,对Prim算法进行介绍的标程都采用了邻接矩阵的储存结构。这种储存方法空间复杂度N2,时间复杂度N2。对于稍微稀疏一点的图,其实我们更适合采用邻接表的储存方式,可以节省空间,并在一定条件下节省时间。

Kruskal算法是依赖边的算法。基本原理是将边集数组排序,然后通过维护一个并查集来分清楚并进来的点和没并进来的点,依次按从小到大的顺序遍历边集数组,如果这条边对应的两个顶点不在一个集合内,就输出这条边,并合并这两个点。
根据Kruskal算法的特性可得,在边越少的情况下,Kruskal算法相对Prim算法的优势就越大。同时,因为边集数组便于维护,所以Kruskal在数据维护方面也较为简单,不像邻接表那样复杂。从一定意义上来说,Kruskal算法的速度与点数无关,因此对于稀疏图可以采用Kruskal算法。

关于去重问题的探讨:
Prim算法的写法需要去重,去重方法就是记录比较最小值
Kruskal算法不需要特意去重,因为我执行m-1次操作

发布了71 篇原创文章 · 获赞 5 · 访问量 3387

猜你喜欢

转载自blog.csdn.net/Rainfoo/article/details/104071958