最小生成树( Prim & Kruskal ) 模板加详解

 

转自:https://www.cnblogs.com/aiguona/p/7223625.html

最小生成树概念:

一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。最小生成树其实是最小权重生成树的简称。

 

prim:

概念:普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。

实现过程:

 

图例说明不可选可选已选(Vnew)
 

此为原始的加权连通图。每条边一侧的数字代表其权值。---

顶点D被任意选为起始点。顶点ABEF通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。C, GA, B, E, FD
 

下一个顶点为距离DA最近的顶点。BD为9,距A为7,E为15,F为6。因此,FDA最近,因此将顶点F与相应边DF以高亮表示。C, GB, E, FA, D
算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。CB, E, GA, D, F
 

在当前情况下,可以在CEG间进行选择。CB为8,EB为7,GF为11。E最近,因此将顶点E与相应边BE高亮表示。C, E, GA, D, F, B
 

这里,可供选择的顶点只有CGCE为5,GE为9,故选取C,并与边EC一同高亮表示。C, GA, D, F, B, E

顶点G是唯一剩下的顶点,它距F为11,距E为9,E最近,故高亮表示G及相应边EGGA, D, F, B, E, C

现在,所有顶点均已被选取,图中绿色部分即为连通图的最小生成树。在此例中,最小生成树的权值之和为39。A, D, F, B, E, C, G

算法模板:

 模板题目链接:http://acm.sdibt.edu.cn/vjudge/contest/view.action?cid=1998#status//-/0/

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<string>
#include<cstring>
#include<set>
#include<cmath>
using namespace std;
#define PI acos(-1)
typedef long long LL;
#define inf 0x3f3f3f3f
const int maxn=1000+10;
int dis[maxn];
int vis[maxn];
int a[maxn][maxn];
int n,m;
void Prim()
{
    int ans=0;
    memset(dis,0,sizeof(dis));
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
        dis[i]=a[1][i];
    dis[1]=0;
    vis[1]=1;
    int i;
    for(i=1;i<n;i++)
    {
        int minn=inf;
        int num=0;
        for(int j=1;j<=n;j++)
        {
            if(dis[j]<minn&&vis[j]==0)
            {
                minn=dis[j];
                num=j;
            }
        }
        if(minn==inf)
            break;
        vis[num]=1;
        ans+=minn;
        for(int j=1;j<=n;j++)
        {
            if(dis[j]>a[num][j]&&vis[j]==0)
                dis[j]=a[num][j];
        }
    }
    if(i<n)
        cout<<"?"<<endl;
    else
        cout<<ans<<endl;
    return ;
}
int main()
{
    while(cin>>m)
    {
        if(m==0)
            break;
        memset(a,inf,sizeof(a));
        cin>>n;
        for(int i=0;i<m;i++)
        {
            int x,y,z;
            cin>>x>>y>>z;
            if(z<a[x][y])
                a[x][y]=a[y][x]=z;
        }
        Prim();
    }
    return 0;
}

Kruskal算法:

 

1.概览

  Kruskal算法是一种用来寻找最小生成树的算法,在剩下的所有未选取的边中,找最小边,如果和已选取的边构成回路,则放弃,选取次小边。

2.实现过程

1).记Graph中有v个顶点,e个边

2).新建图Graphnew,Graphnew中拥有原图中相同的e个顶点,但没有边

3).将原图Graph中所有e个边按权值从小到大排序

4).循环:从权值最小的边开始遍历每条边 直至图Graph中所有的节点都在同一个连通分量中  if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中   添加这条边到图Graphnew中

 

  图例描述:

首先第一步,我们有一张图Graph,有若干点和边 

 

将所有的边的长度排序,用排序的结果作为我们选择边的依据。这里再次体现了贪心算法的思想。资源排序,对局部最优的资源进行选择,排序完成后,我们率先选择了边AD。这样我们的图就变成了下图

  

在剩下的变中寻找。我们找到了CE。这里边的权重也是5

依次类推我们找到了6,7,7,即DF,AB,BE。

下面继续选择, BC或者EF尽管现在长度为8的边是最小的未选择的边。但是现在他们已经连通了(对于BC可以通过CE,EB来连接,类似的EF可以通过EB,BA,AD,DF来接连)。所以不需要选择他们。类似的BD也已经连通了(这里上图的连通线用红色表示了)。最后就剩下EG和FG了。当然我们选择了EG。

代码:

复制代码

//struct A
//{
//    ll x,y;
//    bool operator < (const A & a) const
//    {
//        return x>a.x;
//    }
//};
//priority_queue <A> q;
#include <bits/stdc++.h>
#define eps 0.0000000001
#define mem(a) memset(a,0,sizeof(a))
#define maxx 1e10
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
//priority_queue<ll,vector<ll>,greater<ll> > q;
#define mod 123456789
const ll maxn=5050;

int n,m,ans,sum;
struct A
{
    int st,ed;
    int w;
}a[maxn];
int father[maxn];

bool cmp(A a,A b)
{
    return a.w<b.w;
}
int Find(int x)
{
    if(x!=father[x])
    {
        father[x]=Find(father[x]);
    }
    return father[x];
}
void Union(int x,int y,int n)
{
    int fx=Find(x);
    int fy=Find(y);
    if(fx!=fy)
    {
        father[fx]=fy;
        ans+=a[n].w;
        sum++;
    }
}
int main()
{
    while(cin>>n&&n)
    {
        ans=0;
        m=n*(n-1)/2;
        int x,y,z,e=1;
        for(int i=1;i<=m;i++)
        {
            cin>>x>>y>>z;
            a[i].st=x;
            a[i].ed=y;
            a[i].w=z;
        }
        for(int i=1;i<=m;i++)
        {
            father[i]=i;
        }
        sort(a+1,a+m+1,cmp);
        sum=0;
        for(int i=1;i<=m;i++)
        {
            Union(a[i].st,a[i].ed,i);
            if(sum==n-1)
                break;
        }
        cout<<ans<<endl;
    }
    return 0;
}


复制代码

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值