浅谈最小生成树

    让我们先了解一下什么是最小生成树,就是在一个图里找到一颗树的权值和最小。

一.Kruskal算法

我们现在有如下的一张图:


    求这个图的最小生成树,我们可以先给各权值边从小到大排序,排好序之后进行选边,从最小的开始,从无边开始加。

比如上图,最小的权值边是1和2连通的1,我们把这条边加上,就得到了:

接下重复第一个操作,下一个是1和3之间的权值为2:

接下来是4和6之间的边:

5和6之间的权值:


按照我们的步骤,接下来应该是2和3之间的权值

但是如果我们连接上这两个点,就构成了一个回路,这就不是树了,不能称作最小生成树,所以跳过

接下来,我们继续按顺序进行查找,到了4和5之间


这个不能要的思路和2与3不能连接一样,不能构成最小生成树

接下来就到了3和4之间


此时就已经构成了树,因为已经用了n-1个边,如果再加就不会是树。

这时也构成了这个图的最小生成树。

这就是Kruskal算法的描述,接下来让我们看一下代码实现:

# include <iostream>
# include <cstdio>
# include <algorithm>
using namespace std;
struct edge
{
    int u,v,w;//这里用一个结构体来储存所给的所有关系,便于进行排序和查找。
}e[110];
int n,m;
int f[110]={0};//因为要判断是不是一个树,为了确定他没有回路,需要并查集进行判断。
bool cmp(edge a,edge b)//把边从小到大排序
{
    return a.w<b.w;
}
void init()//并查集的初始化
{
    int i;
    for(i=1;i<=n;i++)
        f[i]=i;
}
int getboss(int v)//并查集找boss
{
    if(f[v]==v)
        return v;
    else
    {
        f[v]=getboss(f[v]);
        return f[v];
    }
}
int merge(int v,int u)//并查集合并两子集合的函数
{
    int t1,t2;
    t1=getboss(v);
    t2=getboss(u);
    if(t1!=t2)
    {
        f[t2]=t1;
        return 1;
    }
    return 0;
}
int main()
{
    int i,temp=0;
    int sum=0;
    scanf("%d%d",&n,&m);//n表示顶点数,m为边的条数。
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    }
    sort(e+1,e+1+m,cmp);//排序
    init();
    for(i=1;i<=m;i++)
    {
        if(merge(e[i].u,e[i].v))//看是否形成环
        {
            temp++;//连通这条边
            sum+=e[i].w;
        }
        if(temp==n-1)//选了n-1条边了,已经形成树了,接下来没必要进行判断了
            break;
    }
    printf("%d\n",sum);
    return 0;
}
/*
 6 9
 2 4 11
 3 5 13
 4 6 3
 5 6 4
 2 3 6
 4 5 7
 1 2 1
 3 4 9
 1 3 2
 
 19
 */


二.Prim算法

我们还是用Kruskal的那个图来讲解Prim算法


因为最小生成树是连通的,可以从一个任意一个点到其他任何点。

所以,我们先任意找一个点,我们选择1这个点,比较符合我们的认知,因为啥事儿都从1开始嘛。

找到1这个点,然后在连通这个点的其他点中找最小的那一条,即 1和2 与 1和3 ,很明显是1到2的那条边,把这两个点连接


然后,继续在连接1,2的线中找权值最小的,即在 1和3 2和3 2和4 中挑最小的那一个,很明显是1和3,连接这两个点。


继续重复上述操作,在连接1,3,2的线中寻找最小的线,我们注意到这些线里2和3的权值最小,但是2这个点已经被连接了,所以,不能找已经连接好的点的权边,这时就应该连接3和4



重复上述操作,如下所示



这就是Prim算法的描述,接下来是代码的实现:

# include <iostream>
# include <cstdio>
# define inf 0x3f3f3f
using namespace std;
int main()
{
    int n,m;//n表示顶点数,m表示边数
    int i,j,k;
    int min;
    int t1,t2,t3;
    int e[110][110];//记录这个图的状态
    int dis[110];//dis数组的使用是这个算法的精髓所在
    //但dis数组的用法和最短路中的不同,Dijkstra算法中的dis是用来记录单源的最小值,即从顶点1到其他边的最小值
    //而在Prim算法中dis数组记录的是已经连接的所有点到其他点的最小值
    int b[110];//记录这个点有没有用过
    int sum=0,temp=0;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=n;j++)
        {
            if(i==j) e[i][j]=0;
            else e[i][j]=inf;
        }
    }
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&t1,&t2,&t3);
        e[t1][t2]=t3;
        e[t2][t1]=t3;//注意这时无向图
    }
    for(i=1;i<=n;i++)
        dis[i]=e[1][i];//我们这是选择1为第一个点
    b[1]=1;//记录1这个点已经使用
    temp++;//用了一个点了
    while(temp<n)//如果temp=n则证明所有点已经连接,可以推出循环了
    {
        min=inf;
        for(i=1;i<=n;i++)
        {
            if(b[i]==0&&dis[i]<min)//找到最小的那个值
            {
                min=dis[i];//更新最小值
                j=i;//记录点
            }
        }
        b[j]=1;//说明该点已经连通
        temp++;
        sum+=dis[j];
        for(k=1;k<=n;k++)
        {
            if(b[k]==0&&dis[k]>e[j][k])//更新已经连通的点到其他点的最小值
                dis[k]=e[j][k];
        }
    }
    printf("%d\n",sum);
    return 0;
}
/*
 6 9
 2 4 11
 3 5 13
 4 6 3
 5 6 4
 2 3 6
 4 5 7
 1 2 1
 3 4 9
 1 3 2
 
 19
 */

猜你喜欢

转载自blog.csdn.net/qq_41181748/article/details/80300964