最小生成树-prim算法和kruskal

前提:
生成树是建立在无向图的基础上的,所以下面的讨论都是在无向图上进行的。
直观的一个应用就是:有n个村庄,现在要在这些村庄之间修一些路,其中村庄i和村庄j之间的距离是Dij,现在要修最短的路使得所有村庄连接起来。

最小生成树( MIT )的经典解决方法有2种:prim算法和kruskal算法。

1.prim算法
算法思想:设图G顶点集合为U,首先任意选择图G中的一点作为起始点a,将该点加入集合V,再从集合U-V中找到另一点b使得点b到V中任意一点的权值最小,此时将b点也加入集合V;以此类推,现在的集合V={a,b},再从集合U-V中找到另一点c使得点c到V中任意一点的权值最小,此时将c点加入集合V,直至所有顶点全部被加入V,此时就构建出了一颗MST。因为有N个顶点,所以该MST就有N-1条边,每一次向集合V中加入一个点,就意味着找到一条MST的边。

算法需要3个数组来实现:
arr[i][j]:存放点到点之间的权值,初始值为最大值,若 是该两点之间有边则改变权值
a[i]:表示以i为终点的边的最小权值,当a[i]=0说明以i为终点的边的最小权值=0,也就是表示i点加入了MST
s[i]:表示对应a[i]的起点,即说明边< s[i],i>是MST的一条边,当s[i]=0表示起点i加入MST

例子:
这里写图片描述

  1. 任取一点作为起始点,假设选 点 v1.

    a[2]=6, a[3]=0, a[4]=5, a[5]=无穷,a[6]=无穷
    s[2]=3, s[3]=0, s[4]=1, s[5]=1, a[6]=1 ,
  2. 可以看出 a[3] 的值最小,所以 点 v3 加入MST,于是更新数组:
    a[2]=5, a[3]=0, a[4]=5,a[5]=6,a[6]=4
    s[2]=3, s[3]=0, s[4]=1, s[5]=3,a[6]=3 ,
  3. a[6]的值最小,所以 点 V6加入 MST,更新数组:
    a[2]=5, a[3]=0, a[4]=2,a[5]=6,a[6]=0
    s[2]=3, s[3]=0, s[4]=6, s[5]=3,a[6]=0 ,
  4. a[4]的值最小,所以点 V4 加入 MST,更新数组:
    a[2]=5, a[3]=0, a[4]=0,a[5]=6,a[6]=0
    s[2]=3, s[3]=0, s[4]=0, s[5]=3,a[6]=0 ,
  5. a[2]数组的值最小,所以点 V2 加入 MST,更新数组:
    a[2]=0, a[3]=0, a[4]=0,a[5]=3,a[6]=0
    s[2]=0, s[3]=0, s[4]=0, s[5]=2,a[6]=0 ,
  6. a[5]数组的值最小,所以点 V5 加入 MST,更新数组:
    a[2]=0, a[3]=0, a[4]=0,a[5]=0,a[6]=0
    s[2]=0, s[3]=0, s[4]=0, s[5]=0,a[6]=0 ,
    所有的点都加入到了MST中,结束。
    这里写图片描述
//最小生成树算法,输出点路径以及权值 
#include<iostream>
using namespace std;
const int maxn=1000;
const int maxnum=100000;
int arr[maxn][maxn];
int a[maxn];
int s[maxn];
int n,m;

void prims(int begin)
{
    cout<<"路径:"<<begin<<" ";
    int i,j,minum,minindex,sum=0;
    for(i=1;i<=n;i++)
    {
        a[i]=arr[begin][i];
        s[i]=begin;
    }
    s[begin]=0;
    for(i=1;i<=n;i++)
    {
        if(i==begin)
            continue;

        minum=maxnum;
        for(j=1;j<=n;j++)
        {
            if(a[j]<minum&&a[j])
            {
                minum=a[j];
                minindex=j;
            }
        }
        a[minindex]=0;
        s[minindex]=0;
        cout<<minindex<<" ";

        sum+=minum;
        for(j=1;j<=n;j++)
        {
            if(s[j]&&arr[minindex][j]<a[j])
            {
                s[j]=minindex;
                a[j]=arr[minindex][j];
            }
        }
    }   
    cout<<endl;
    cout<<"权值:"<<sum;
}

int main()
{
    int i,j,a,b,c,begin;
    //初始化存放点的数组 
    cin>>n>>m;
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            arr[i][j]=maxnum;
    while(m--)
    {
        cin>>a>>b>>c;
        arr[a][b]=arr[b][a]=c;
    }       

    cout<<"输入起始点:"<<endl;
    cin>>begin;

    prims(begin);

    return 0;
} 

测试数据:
6 10
1 2 6 
1 3 1 
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
4 6 2
5 6 6
3

结果:
这里写图片描述
2.kruskal算法 (使用并查集实现)

例子:
这里写图片描述

以图为例,kruskal算法的步骤:

  1. 取得权值最小的边 v1 到 v3
  2. 在不构成一个圈的情况下,取得权值次小的边:v4 到 v6
  3. 在不构成一个圈的情况下,取得下一条边:v2 到 v6
  4. 在不构成一个圈的情况下,取得下一条边:v3 到 v6
  5. 在不构成一个圈的情况下,取得下一条边:v2 到 v3

实现代码:

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000;
struct node
{
    int start,end,value;
    bool operator < (const node &n)const
    {
        return value<n.value;
    }
}arr[maxn];
int n,m;//count:若有最小生成树,则为最小权值和 
int fa[maxn];//

int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
void kruskal()
{
    int i,ans,count;
    node e;
    ans=0;count=0; 

    //初始化fa数组
    for( i=1;i<=n;i++)
        fa[i]=i; 
    for(i=0;i<m;i++)
    {
        int x=find(arr[i].start);
        int y=find(arr[i].end);
        if(x!=y)
        {
            fa[x]=y;
            ans++;
            count+=arr[i].value;
        }
    }
    if(ans<n-1)
    {
        cout<<"最小生成树不存在"<<endl;
    }else{
        cout<<count<<endl;
    }


}
int main()
{
    int i,a,b,c;

    cin>>n>>m;//n:点数 m:边数 
    for(i=0;i<m;i++)
    {
        cin>>a>>b>>c;
        arr[i].start=a;
        arr[i].end=b;
        arr[i].value=c;
    }
    sort(arr,arr+m); 
    kruskal();

    return 0;
}

测试数据:
6 10
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
4 6 2
5 6 6

实验结果:
15

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值