最小生成树(Prim / Kruskal)

Kruskal算法(加边法)

思路:

首先对边的权值从小到大进行排序,而后遍历查看每一条边,循环以下步骤:

1)若该边两端顶点分属不同连通分量,则将此边加入,之后将其两端顶点合并为同一个连通分量;

2)若该边两端顶点已属于同一连同分量,则舍弃,继续查看下一条权值最小的边。

Code:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1000;//最大边数
int N,M;//N个顶点,M条边
struct node{
    int u,v,w;
    bool operator < (const node b)const{
        return w < b.w;
    }
}G[MAXN],T[MAXN];
void Kruskal()
{
    int f[N+5]={},vs1,vs2,Sum=0,q=0;
    sort(G,G+M);//对边的权从小到大排序
    for(int i=0;i<=N;i++)//表示各顶点自成一个连通分量
        f[i]=i;
    for(int i=1;i<=M;i++){
        vs1=f[G[i].u];//获取边G[i]的始点所在的连通分量vs1
        vs2=f[G[i].v];//获取边G[i]的终点所在的连通分量vs2
        if(vs1!=vs2){//边的两个顶点分属不同的连通分量
            T[q].u=G[i].u,T[q].v=G[i].v,T[q++].w=G[i].w;//储存最小生成树的每一条边
            Sum+=G[i].w;
            for(int k=0;k<=N;k++)//合并vs1和vs2两个分量,即两个集合统一编号
                if(f[k]==vs2)//集合编号vs2的都改为vs1
                    f[k]=vs1;
        }
    }
    if(q!=N-1)cout<<"该图不连通"<<endl;
    else{
        cout<<"最小生成树的边权之和:"<<Sum<<endl;
        for(int i=0;i<q;i++)//输出最小生成树的每一条边
            cout<<T[i].u<<' '<<T[i].v<<' '<<T[i].w<<endl;
    }
}
int main()
{
    cin>>N>>M;//输入顶点数和边数
    for(int i=1;i<=M;i++)
        cin>>G[i].u>>G[i].v>>G[i].w;//输入每条边的两个顶点和边权
    Kruskal();
    return 0;
}

可用并查集判断边的两个顶点是否已属于同一连通分量:

void init(int n)
{
    for(int i=0;i<=n;i++){
        f[i]=i;
    }
}
int get(int x)
{
    if(f[x]==x)
        return x;
    return get(f[x]);
}
int merge(int u,int v)
{
    int t1,t2;
    t1=get(u);
    t2=get(v);
    if(t1!=t2){
        f[t2]=t1;
        return 1;
    }
    return 0;
}

Prim算法(加点法)

思路:

①需建立一个辅助数组dis维护已选取加入的点到其余各个顶点的边的最小权值。
②需建立一个标记数组标记哪个顶点已选取加入。
首先选取任意一个顶点加入,建立上述的dis数组,标记该顶点已选取加入,之后循环以下步骤n-1次:

(1)遍历dis数组找其中记录的边权值最小且未被选取的点加入,标记该点已选取,连接该边的两点;

(2)更新维护dis数组。

Code:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1000;
const int inf=0x3f3f3f3f;
int N,M,G[MAXN][MAXN];

void Init()
{
    for(int i=0;i<=N;i++)
        for(int j=0;j<=N;j++)
            G[i][j]=inf;
}
void Prim()
{
    pair<int,int>dis[N+5];//dis[i],第二个存当前其余顶点到该点i的边最小权值,第一个存当前该最小权值边与该点i相连的起点
    int Sum=0,book[N+5]={};
    for(int i=0;i<=N;i++)//选取任意一个顶点加入,此处选择编号1的点为第一个加入的点
        dis[i]=make_pair(1,G[1][i]);
    dis[1].second=0;
    book[1]=1;//标记已选取
    for(int i=1;i<=N-1;i++){
        int Min=inf,k,u0,v0,w0;
        for(int j=0;j<=N;j++){
            //cout<<dis[j].second<<' ';//输出dis数组信息
            if(!book[j]&&dis[j].second<Min){//寻找当前dis数组中的最小边权dis[j].second,j则为下一个选取的点
                Min=dis[j].second;
                k=j;
            }
        }
        book[k]=1;//标记已选取
        u0=dis[k].first, v0=k, w0=Min;
        cout<<u0<<' '<<v0<<' '<<w0<<endl;//输出最小生成树的边
        Sum+=Min;
        dis[k].second=0;
        for(int u=0;u<=N;u++){//维护更新dis数组
            if(!book[u]&&dis[u].second>G[k][u])
                dis[u]=make_pair(k,G[k][u]);
        }
    }
    cout<<"最小生成树的边权之和:"<<Sum<<endl;
}
int main()
{
    int u,v,w;
    cin>>N>>M;//输入顶点数和边数
    Init();//建图前将每个点之间的边初始化为无穷大
    for(int i=0;i<M;i++){
        cin>>u>>v>>w;//输入每条边的两个顶点和边权
        G[u][v]=w,G[v][u]=w;
    }
    Prim();
    return 0;
}

(备注:因为有些测试用例的顶点编号为0~N-1,而有些是1~N,为了代码对两者都的适用,上述两份代码对顶点的for遍历都是从0到N,故可输入的顶点编号范围为0~N。)

原文地址:https://www.cnblogs.com/HOLLAY/p/11944167.html