zoukankan      html  css  js  c++  java
  • 最小生成树问题

    最小生成树:

     

    一个有N个点的图,边一定是大于等于N-1条的。图的最小生成树,就是在这些边中选择N-1条出来,连接所有的N个点。这N-1条边的边权之和是所有方案中最小的。

    最小生成树用来解决什么问题?   

    就是用来解决如何用最小的“代价”用N-1条边连接N个点的问题。

    例题:洛谷P3366

    乾坤大挪移

    最小生成树共有两种算法:

    prim算法与Kruskal算法

    1.prim算法

    Prim算法采用与Dijkstra、Bellman-Ford算法一样的“蓝白点”思想:

    白点代表已经进入最小生成树的点,蓝点代表未进入最小生成树的点。

    算法思想:

    以1为起点生成最小生成树,min[v]表示蓝点v与白点相连的最小边权。

    MST表示最小生成树的权值之和。

    1)初始化:min[v]= ∞(v≠1); min[1]=0;MST=0; b)for (i = 1; i<= n; i++) 1.寻找min[u]最小的蓝点u。

    2.将u标记为白点

    3.MST+=min[u]

    4.for 与白点u相连的所有蓝点v if (w[u][v]<min[v]) min[v]=w[u][v]; c)

    算法结束: MST即为最小生成树的权值之和

    推论:

    初始时所有点都是蓝点,min[1]=0,min[2、3、4、5]=∞。权值之和MST=0。

    第一次循环自然是找到min[1]=0最小的蓝点1。将1变为白点,接着枚举与1相连的所有蓝点2、3、4,修改它们与白点相连的最小边权。

    第二次循环是找到min[2]最小的蓝点2。将2变为白点,接着枚举与2相连的所有蓝点3、5,修改它们与白点相连的最小边权。

    第三次循环是找到min[3]最小的蓝点3。将3变为白点,接着枚举与3相连的所有蓝点4、5,修改它们与白点相连的最小边权。   

    最后两轮循环将点4、5以及边w[2][5],w[3][4]添加进最小生成树。

    最后权值之和MST=6。

    这n次循环,每次循环我们都能让一个新的点加入生成树,n次循环就能把所有点囊括到其中;

    每次循环我们都能让一条新的边加入生成树,n-1次循环就能生成一棵含有n个点的树;

    每次循环我们都取一条最小的边加入生成树,n-1次循环结束后,我们得到的就是一棵最小的生成树。    

    这就是Prim采取贪心法生成一棵最小生成树的原理。 算法时间复杂度:O (n2)。

    例题代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    #define inf 0x7fffffff
    #define maxn 5005
    int cost[maxn][maxn],minn,n,m,v2[maxn],tot=1,now,ans;
    bool v1[maxn];
    inline void getcost()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                cost[i][j]=inf;
            }
        }
        //先将数组赋为极大值
        for(int i=1,u,v,w;i<=m;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            if(cost[u][v]>w)
            {
                cost[u][v]=cost[v][u]=w;
            }
        }
        //初始化cost数组
        for(int i=1;i<=n;i++)
        {
            v2[i]=cost[1][i];
        }
        v1[1]=1;
        //找出与1节点相连的边并进行标记
    }
    inline int prim()
    {
        while(tot<n)
        //最小生成树的概念,边数比点数小一
        {
            minn=inf; 
            //附上初值
            tot++;
            //已经使用边数++
            for(int i=1;i<=n;i++)
            {
                if(!v1[i]&&v2[i]<minn)
                {
                    minn=v2[i];
                    now=i;
                }
            }
            //找出与该点相连的最小边
            ans+=minn;
            //更新答案
            for(int i=1;i<=n;i++)
            {
                if(v2[i]>cost[now][i]&&!v1[i])
                {
                    v2[i]=cost[now][i];
                }
            }
            v1[now]=1;
            //在找出与now节点相连的边并进行标记
        }
        return ans;
    }
    int main()
    {
        getcost();
        printf("%d",prim());
        return 0;
    }

    2.Kruskal算法

    算法描述:

    1.初始化并查集。father[x]=x。

    2.tot=0

    3.将所有边用快排从小到大排序。

    4.计数器 k=0;

    5.for (i=1; i<=M; i++) //循环所有已从小到大排序的边

    if 这是一条u,v不属于同一集合的边(u,v)(因为已经排序,所以必为最小)

    begin  

    ①合并u,v所在的集合,相当于把边(u,v)加入最小生成树。  

    ②tot=tot+W(u,v)

    ③k++

    ④如果k=n-1,说明最小生成树已经生成,则break; end;

    6. 结束,tot即为最小生成树的总权值之和。

    Kruskal算法的时间复杂度为O(E*logE),E为边数。

    例题代码:

    #include<iostream>
    #include<cstring>
    #include<cstdio> 
    #include<algorithm>
    using namespace std;
    int r[5005],n,m,ans,w,e,cnt;
    struct Edge
    {
        int u,v,w;
    }
    edge[222222];
    bool cmp(Edge a,Edge b)//交换函数 
    { 
        return a.w<b.w; 
    }
    int find(int x)
    {
        while(x!=r[x]) x=r[x]=r[r[x]];
        return x;
    }
    void kruskal()//算法精华 
    {
        sort(edge,edge+m,cmp);
        for(int i=0;i<m;i++){
            w=find(edge[i].u), e=find(edge[i].v);
            if(w==e) continue;
            ans+=edge[i].w;
            r[e]=w; cnt++;
            if(cnt==n-1) break;
        }
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) 
        r[i]=i;
        for(int i=0;i<m;i++)
        scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
        kruskal();
        printf("%d",ans);
        return 0;
    }

    例题2:

    洛谷P1546

    传送门

    运用kruskal算法解决

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    struct point
           {
              int x;
              int y;
              int v;
           };
    point a[9901];
    int fat[101];
    int n,i,j,x,m,tot,k;
    int father(int x)
    {
        if (fat[x] != x) fat[x] = father(fat[x]);
        return fat[x];   
    }
    void unionn(int x,int y)
    {
        int fa = father(x);
        int fb = father(y);
        if (fa != fb) fat[fa] = fb;  
    }
    int cmp(const point&a,const point&b)
    {
         if (a.v < b.v) return 1;
            else return 0; 
    }
    int main()
    {
        cin >> n;
        for (i = 1; i <= n; i++)
            for (j = 1; j <= n; j++)
            {
                cin >> x;
                if (x != 0)
                {
                    m++;
                    a[m].x = i; a[m].y = j; a[m].v = x;
                 }
            }
        for (i=1;i<=n;i++) 
            fat[i]=i;
        sort(a+1,a+m+1,cmp); 
            for(i=1;i<=m;i++)
            {
                if (father(a[i].x) != father(a[i].y))
                {
                    unionn(a[i].x,a[i].y);
                    tot += a[i].v;
                    k++;
                }
            if (k == n-1) break;
            }
        cout << tot;
        return 0;
    }
  • 相关阅读:
    rals gb2312编码
    义乌给力电子商务有限公司招聘网络推广专员
    [PHP]APACHE模块化安装PHP以及MySQL 配置@Windows(PHP5)
    wordpress将远程数据下载一份到本地20110228操作过程记录
    百万邮件群发业务
    Ruby 读写文件
    万网备案专题
    新ubuntu配置过程记录
    wordpress将远程数据下载一份到本地20110228操作过程记录
    万网备案专题
  • 原文地址:https://www.cnblogs.com/U58223-luogu/p/9559449.html
Copyright © 2011-2022 走看看