最小生成树

本文深入探讨了最小生成树的概念及其在图论中的应用,详细介绍了两种主要算法——Kruskal算法和Prim算法的实现原理及步骤。通过具体实例,展示了如何在有回路的连通无向图中寻找花费最小的无回路连通子图。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是树?

树:由边和点组成,n个点,以n-1条边使任意两个点连通。(无回路的连通图

什么是最小生成树?

给定一个有回路的连通无向图,有n个点,m条边(m>n),每条边都有一个花费值,在此有回路的连通无向图中,找到一个花销最小的无回路的连通图。

求解最小生成树的方法

一、Kruskal算法(选边)

Kruskal算法:
首先按照边的权值进行升序排序,每次从剩余的边中选择权值最小两点不连通的边加入到生成树中,直到加入n-1条边为止。

如何判断两点是否连通呢?
判断两个点是否连通,只需判断两个点是否在同一个集合,如果在同一个集合,则两点是连通的,否则不是。
#使用并查集(在并查集中,同一集合的点,最终老大是一样的)


实现步骤:
一个含有n个点的树,有n-1条边。由此,我们想到选择n-1条边使n个点连通。
对于给定的有回路的连通无向图

  1. 把它所有的边按花费的升序排序,(边是由两个点表示的,如1 2,代表从1到2的那条边)
  2. 判断第i条边的两个点是否已经连通
    1. 如果连通,则不加入生成树
    2. 如果没有连通,则加入生成树
  3. 直至找到n-1条边为止

kruskal代码实现:
时间复杂度:O(MlogM)

//最小生成树
#include<stdio.h>
#include<algorithm>
using namespace std;
struct edge{
    int u;
    int v;
    int w;;
};

int f[7]={0};
//寻找点v的最终老大
int getf(int v){
    if(f[v]==v)
        return v;
    else
    {
        f[v]=getf(f[v]);
        return f[v];
    }
}

int merg(int u,int v){
    int t1,t2;
    t1=getf(u);
    t2=getf(v);
    if(t1!=t2){
        f[t2]=t1;
        return 1;
    }
    return 0;
}
int cmp(edge e1,edge e2){
    return e1.w<e2.w;
}

int main(){

    edge e[10];
    int n,m;
    int sum=0;
    int coun=0;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);

    sort(e,e+m,cmp);

    for(int i=0;i<n;i++)
        f[i]=i;

    for(int i=0;i<m;i++){

        if(merg(e[i].u,e[i].v)){
            coun++;
            sum+=e[i].w;
        }
        if(coun==n-1)
            break;
    }
    printf("%d\n",sum);
}

二、Prim算法(选点)

Prim算法:
首先选择一个顶点加入生成树,然后选择生成树中的所有连接着未入树的点的出边,选择一个最小的边连着的点加入生成树,重复n-1次,即加入n个点,则找到最小生成树。


实现步骤:
我在想,既然是选择点,并且是选择够n个点为止。

  1. 首先随便选择一个点,加入到生成树中,然后在生成树中选择一个未入树的点的最小出边对应的点,入树。这个时候涉及到一个最短路径问题。

如何知道哪个点离生成树最近?
刚开始的时候,生成树中只有一个点,dis[i]就代表第i个点到某某某的最短距离,这个某某某生成树,(和最短路到某某点是一个性质的)

  1. 每加入一个点到生成树中,需要更新一下其他点到生成树的最短距离。

但是为什么呢?
比如有三个点1 2 3
三条边
1->2 3
2->3 3
1->3 7

dis[1]=0 dis[2]=3 dis[3]=7
现在加入1,1的出边有2和3,选择最短的2,加入。
更新到点i到生成树的最短距离dis[点i]
dis[1]=0 dis[2]=0(这个不用更新了) dis[3]=3
这样的话,点i到生成树的距离就有了更新,所以每次选一个点之后需要更新一下到生成树的最短距离。

  1. 直到加入n个点为止,返回一个sum值是最小花费。

Prim代码实现:
时间复杂度:O(N^2)

#include<stdio.h>
#include<string.h>
int e[10][10];
int inf=0xffffff;
//返回一个int的最小花费
int Prim(int n){
    int book[10];
    int dis[10];
    int coun=0,sum=0,t;
    memset(book,0,sizeof(book));
    book[1]=1;
    coun++;
    //设置每个点到生成树的最短距离,刚开始生成树中只有点1
    for(int i=1;i<=n;i++)
        dis[i]=e[i][1];

    while(coun<n){
        int min_=inf;
        //找一个到生成树距离最小且没有加入生成树的点
        for(int i=1;i<=n;i++){
            if(!book[i]&&dis[i]<min_){
                min_=dis[i];
                t=i;
            }
        }
        coun++;
        book[t]=1;
        sum+=min_;

        //更新没加入生成树的点到生成树的距离
        for(int i=1;i<=n;i++){
            if(!book[i]&&dis[i]>e[i][t])
                dis[i]=e[i][t];
        }
    }
    return sum;
}
int main(){
    int n,m;
    int x,y,z;
    scanf("%d%d",&n,&m);

    //录入边值
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            if(i==j)
                e[i][j]=0;
            else
                e[i][j]=inf;
        }
    for(int i=0;i<m;i++){
        scanf("%d%d%d",&x,&y,&z);
        e[x][y]=z;
        e[y][x]=z;
    }

    printf("%d",Prim(n));
}

测试数据
输入:
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算法,并且使用邻接表存图,可使其复杂度有O(N^2)降到O(MlogN),后续再更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值