最小生成树问题

最小生成树:

 

一个有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;
}
原文地址:https://www.cnblogs.com/U58223-luogu/p/9559449.html