最小生成树:克鲁斯卡尔算法、prim(普里姆)算法

最小生成树

1.克鲁斯卡尔算法

\bullet 克鲁斯卡尔算法的实质就是加边,先对边进行从小到大排序,然后再从小的边开始加进树里,但是不能构成环。重复上述步骤,直至树里面有n-1条边(总共有n个结点)

原始图:(从1号点开始)

第一次:
在这里插入图片描述
第二次:
在这里插入图片描述
第三次:
在这里插入图片描述
第四次:
在这里插入图片描述
好了,最小生成树就构造好了。
\bullet 例题:
某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
Input
测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
当N为0时,输入结束,该用例不被处理。
Output
对每个测试用例,在1行里输出最小的公路总长度。
Sample Input

3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0

Sample Output

3
5

\bullet 代码运用到了并查集:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
const int maxn=1e4+10;
const int inf=999999999;
int par[maxn];
int k,m,n;
struct node
{
    int s,e,val;
}e[maxn];

int cmp(node a,node b)
{
    return a.val<b.val;
}

int Find(int x)
{
    if(x==par[x])return x;
    else {
        par[x]=Find(par[x]);
        return  par[x];
    }
}

void join(int a,int b)
{
    int fa=Find(a);
    int fb=Find(b);
    if(fa!=fb){
        par[fb]=fa;
    }
}

void init()
{
    for(int i=1;i<=n*(n-1)/2;i++)par[i]=i;
    memset(e,0,sizeof(e));
}


int main()
{
    while(scanf("%d",&n)&&n)
    {
        init();
        int N=n*(n-1)/2;
        for(int i=1;i<=N;i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            e[i]={a,b,c};
        }
        sort(e+1,e+N+1,cmp);//对边进行排序
        int sum=0;
        int flag=0;
        for(int i=1;i<=N;i++){
            if(Find(e[i].s)!=Find(e[i].e)&&(flag<=n-1)){//要合并的两个点的祖先结点不能一样,一样就要构成环了,这是不符合最小生成树的定义的
                join(e[i].s,e[i].e);
                flag++;
                sum+=e[i].val;
            }
            else if(flag>=(n-1))break;
        }
        printf("%d\n",sum);
    }
    return 0;
}

2.prim(普里姆)算法

\bullet 普里姆算法的实质就是加点,设有两个集合:U和V,U代表已经加入树中的结点,V代表还未加入树的结点。最开始U中没有结点。
首先有个dis[ i ]数组(初始化为无穷大),代表第 i 个节点与把它更新的结点之间的边权算法主要有两个步骤:
1.找到最小的dis[ i ]
2.更新与 i 结点有边的结点
原始图:(从1号点开始)
在这里插入图片描述
第一次:
在这里插入图片描述
第二次:
在这里插入图片描述

第三次:
在这里插入图片描述

第四次:
在这里插入图片描述
一个最小生成树就构造好了。
\bullet 例题(还是上面的例题):

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
const int maxn=1e3+10;
const int inf=999999999;
int n,m;
int mp[maxn][maxn];
int vis[maxn],dis[maxn];

int prim(int start)
{
   int ans=0;
   dis[start]=0;
   for(int i=1;i<=m;i++){
       int minn=inf;
       int now=0;
       for(int j=1;j<=m;j++){//在未加入树中的结点中找最小的边
           if(!vis[j]&&dis[j]<minn){
               minn=dis[j];
               now=j;
           }
       }
       ans+=dis[now];//将点和边加入树中
       vis[now]=1;
       for(int j=1;j<=m;j++){//更新与刚才找到的now点有边相连的点
           if(!vis[j]&&mp[now][j]<dis[j])
               dis[j]=mp[now][j];
       }
   }
   return ans;
}

void init(){
   for(int i=1;i<=m;i++){
       dis[i]=inf;
       for(int j=1;j<=m;j++){
           mp[i][j]=inf;
       }
       mp[i][i]=0;
   }
   for(int i=1;i<=m;i++)vis[i]=0;
}

int main()
{
   while(scanf("%d",&m)&&m){///m个点
       init();
       n=m*(m-1)/2;
       for(int i=1;i<=n;i++){
           int a,b,c;
           scanf("%d%d%d",&a,&b,&c);
           mp[a][b]=min(mp[a][b],c);
           mp[b][a]=min(mp[a][b],c);
       }
       int ans=prim(1);
       printf("%d\n",ans);
   }
   return 0;
}

发布了34 篇原创文章 · 获赞 7 · 访问量 1886

猜你喜欢

转载自blog.csdn.net/qq_43628761/article/details/96340613