• 浅说——最小生成树


    图论基础知识

    设有图G(V,E).

    w(u,v)表示边(u,v)的权。

    生成树是G的极小连通子图,它包含原图的n个点和n-1条边,且是连通的。

    若存在树T,使得边权之和W(T)最小,则T为最小生成树。

    例:(来了来了…)

    要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信。

    但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同。

    次要目标是要使铺设光缆的总费用最低。

     这里讲两种方法:

    Kruskal算法(克鲁斯卡尔):贪心策略,不断加边

    Prim算法(普里姆):贪心策略,不断加点

    Kruskal算法:

    先构造一个只含 n 个顶点,而边集为空的子图。(开始加边)

    之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取(已经到过了)

    而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。

    从最短边开始加,若未经历节点,则加入树。

    度娘

    伪代码

    sort(e+1,e+m+1);
    初始化MST=NULL;
    初始化各点各自为一个集合;
    for(int i=0;i<m;i++)
    {
    if(e[i].u和e[i].v不在一个集合) { 将e[i]加入MST; 合并e[i].u和e[i].v所在的集合; }
    }

    该算法中关键在于解决判断u,v是否在同一集合和将其合并的操作,这里我们使用一种简单高效的方法:并查集

    并查集是一种树型的数据结构,用于处理一些点所在集合的合并及查询问题。常常在使用中以森林来表示。

    算法步骤

    初始化:把每个点所在集合初始化为其自身。

    查找:查找元素所在的集合,即根节点。

    合并:将两个元素所在的集合合并为一个集合。合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。

    void chushi()
    {//初始化
    for(int i=0;i<n;i++)father[i]=i;
    }
    
    int find(int x)
    {//查找
    if(father[x]==x)return x;
    return father[x]=find(father[x]);
    }//路径压缩
    
    void unionset(int x,int y)//合并
    {
     int fx=find(x);//查找x的所在树的根
     int fy=find(y);//查找y的所在树的根
     if(fx!=fy)father[fx]=fy;//将x所在集合与y所在集合合并
    }

    自己拿演草纸推论一下就出来了

    RQ193 造路行动Kruskal

    模板题:

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn=1005;
    int n,m,ans;
    int fa[maxn];
    struct edge{
        int x,y,v;
    }e[maxn*maxn];
    int find(int x)  //查找父亲节点 
    {
        if(fa[x]==x) return x;
        return fa[x]=find(fa[x]);
    }
    bool cmp(struct edge a,struct edge b)
    {
        return a.v<b.v;
    }
    void kruskall()
    {
        int cnt=0;
        sort(e+1,e+m+1,cmp);
        for(int i=1;i<=n;i++) fa[i]=i;
        for(int i=1;i<=m;i++)
        {
            if(cnt==n-1) break;        
            int fx=find(e[i].x);
            int fy=find(e[i].y);
            if(fx!=fy)    
            {
                fa[fx]=fy;     //合并两棵树 
                cnt++;
                ans+=e[i].v;
            }        
        }
        printf("%d",ans);
    }
    int main()
    {
        int x,y,a;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++) 
        {
            scanf("%d%d%d",&x,&y,&a);
            e[i].x=x;
            e[i].y=y;
            e[i].v=a;            
        }
        kruskall();
        return 0;
    }

    Prim算法

    1、设立一个只有结点u0的结点集U和一个空的边集E作为最小生成树的初始形态;

    2、在所有u∈U,v∈(V-U)的边(u,v)∈E中,找一条权最小的边(u0,v0),将此边加进集合E中,并将此边的非U中顶点加入U中。

    3、如果U=V,则算法结束;否则重复步骤2。

    度娘

    还是看图吧

    伪代码

    for(int i=1;i<=n;i++)dis[i]=inf;
    dis[1]=0;
    for(int i=1;i<=n;i++)
    {
    for(int j=1;j<=n;j++)
    找dis[j]最小&&vis[j]==0的点j,
    vis[j]=1;
    更新j->k的dis[k];
      }

    RQ193 造路行动prim

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define inf 0x3f3f3f3f
    using namespace std;
    const int maxn=1005;
    int n,m,ans,cnt=1;
    int fa[maxn],head[maxn],dis[maxn],vis[maxn];
    struct edge{
        int x,y,v,next;
    }e[maxn*maxn];
    
    void addedge(int x,int y,int a)//邻接矩阵
    {
        e[cnt].x=x;
        e[cnt].y=y;
        e[cnt].v=a;
        e[cnt].next=head[x];
        head[x]=cnt++;
    }
    void primm()
    {
        int mi;
        memset(dis,inf,sizeof(dis));
        dis[1]=0;
        int u;
        for(int i=1;i<=n;i++)
        {
            mi=inf;
            for(int j=1;j<=n;j++)
                if(!vis[j]&&mi>dis[j]) 
                {
                    mi=dis[j];u=j;    
                }
            vis[u]=1;
            ans+=mi;
            for(int k=head[u];k!=-1;k=e[k].next)
            {
                if(dis[e[k].y]>e[k].v)
                    dis[e[k].y]=e[k].v;
    
            }
            
        }
        printf("%d",ans);
    }
    int main()
    {
        int x,y,a;
        scanf("%d%d",&n,&m);
        memset(head,-1,sizeof(head));
        for(int i=1;i<=m;i++) 
        {
            scanf("%d%d%d",&x,&y,&a);
            addedge(x,y,a);    
            addedge(y,x,a);        
        }
        primm();
        return 0;
    }

    时间复杂度

    Kruskal:O(MlogM)

    Prim:O(N^2),可以用二项堆优化到O(MlogV)。

    对于稠密图Prim更好,稀疏图Kruskal更佳(且算法更简)

  • 相关阅读:
    牛客网剑指offer第46题——孩子们的游戏(圆圈中最后剩下的数)
    不借助临时变量两数交换篇
    牛客网剑指offer第48题——不用加减乘除做加法
    牛客网剑指offer第44题——翻转单词顺序列
    双指针索引技术
    二叉树的下一个结点
    数组中的逆序对
    丑数
    野指针与空指针
    【转】以操作系统的角度述说线程与进程
  • 原文地址:https://www.cnblogs.com/mzyczly/p/11025043.html
Copyright © 2020-2023  润新知