• 最小生成树(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。)

  • 相关阅读:
    Linux操作_常用命令操作练习
    Linux编程_Shell脚本练习题
    Linux操作_grep/egrep工具的使用
    Linux中的链接文件_软链接和硬链接
    Linux操作_磁盘管理_增加虚拟磁盘
    Linux命令_磁盘管理_查看磁盘或目录的容量
    Linux命令_用户身份切换
    使用Unity中的Box Collider组件完成游戏场景中的碰撞检测功能
    在Unity场景中更改天空盒的步骤
    Linux命令_用户和用户组管理
  • 原文地址:https://www.cnblogs.com/HOLLAY/p/11944167.html
Copyright © 2020-2023  润新知