最小生成树[Kruskal&&Prim](学习)

本文深入浅出地介绍了最小生成树的概念及其两种经典算法——Prim算法和Kruskal算法。不仅详细解析了这两种算法的工作原理,还提供了丰富的示例代码帮助读者理解算法实现。

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

本文是我边学习边写下的博客,望大家一起学习,错误请指出。主要我会以代码显现些图联通的过程,如果并不能领悟,建议大家去看一些有图解析的博客吧,我们再来领略些入门题目,不断更新呗-_-。


**最小生成树:**一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。要求图权重和最小,通俗的讲,我们没办法再找到另一个边去代替树中的边使得图的权重和更小。

Prim与kruskal全是应用了贪心的原则!都是选取当前未访问边的最小边,不过Kruskal是选取全图中最小未访问边,而Prim是选取与已经选取的集合相连的最小边,看不懂没关系,看完下面回过头来思考下就会明白。


Prim:
学过最短路吗?没学过不打紧,学过肯定能发现 Dijkstra 最短路径和Prim最小生成树算法真的是双胞胎啊,只是在更新dist时的if条件不同,以及要求的量不同。这里讲一下闲话,好像是Prim先研究出来的哦(我就默认了)。
两兄弟都是根据贪心思想,既然Prim先出来,我们就讲讲哥哥Prim,按照我的理解,Prim设置了两个 点集合,一个集合为要求的生成树的点集合A,另一个集合为未加入生成树的点集合B。Prim算法是基于B所有点到A的距离计算的,然后以贪心原则选最小的边,所以他和Dijkstra一样空间复杂度为(V^2)。
它的实现过程是:开始时:A集合是空的,B集合有着好多点呢(B房子一个点一个点地往A房子送)。我们有个起始点,寻找和起始点相连的最短边,将其对应相连的点往A送,送到A后我们看到这个刚加入的点还连带着一些边呢,我们把与A集合相连的一些未连接的点最小的距离重置下(可能刚进来的点连着的边比原来更小呢,是吧),我们不断把与A集合相连的最小边的点加进来,直到我们把所有点连接。(看不懂,没关系的,继续)

我们再顺带讲讲弟弟Dijkstra(可以忽略),它的目的是求某个源点到目的点的最短距离,总的来说,Dijkstra算法也就是求某个源点到目的点的最短路,求解的过程也就是求源点到整个图的最短距离。
其实他也基本一样,Prim我们把与A集合相连的一些未连接的点最小的距离重置下,不断把与A集合相连的最小边的点加进来;而Dijkstra 我们不断用松弛操作把与起始点相连的一些未连接的点最小的距离重置下,不断把与起始点相通的最小边的点加进来。

来来来,看代码理解Prim:
小二,来个入门题!
好咧!
http://poj.org/problem?id=1258
给了个矩阵图,求最小生成树。
案例:

4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0
————————————
28

以下是联通过程:
这里写图片描述

//拿自己的图试一下吧。
#include<iostream>
#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;
const double PI = acos(-1.0);
const int mod = 1e9+7;
const int N = 1010;
int n,m;

int v[N];		//标记数组
int p[N][N];    //矩阵图
int dis[N];     //每个点到集合A的最短距离
void init()
{
    for(int i =1; i <= n; i++)
        for(int j = 1; j <= n; j++)
    {
        if(i == j) p[i][j] = 0;
        else p[i][j] = INF;
    }
}
void Prime()
{
    int record[N];
    int k = 1;
    int ans = 0;
    for(int i = 1; i <= n; i++)
    {
        v[i] = 0;
        dis[i] = INF;
    }
    dis[1] = 0;
    int next;
    for(int i = 1; i <= n; i++)
    {
        printf("集合:{");
        for(int j = 1; j <= n; j++)
        {
            if(v[j])
                printf(" %d",j);
        }
        printf(" }\n");
        int MIN = INF;
        for(int j = 1; j <= n; j++)
        {
            if(!v[j])
            {
                if(dis[j] != INF)
                    printf("点%d与集合的最短距离:%d\n",j,dis[j]);
                else
                    printf("点%d与集合的最短距离:INF\n",j);
            }
            if(dis[j] < MIN && !v[j])
            {
                MIN = dis[j];
                next = j;
            }
        }
        if(MIN == INF)
            break;
        printf("第%d次往A集合加入: %d点 值为: %d\n",k,next,MIN);
        record[k] = next;
        ans += MIN;
        k++;
        v[next] = 1;
        for(int j = 1; j <= n; j++)
        {
            if(!v[j] && dis[j] > p[next][j])
                dis[j] = p[next][j];
        }
    }
    printf("A集合加入顺序:{");
    for(int i = 1; i < k; i++)
        printf("%d->",record[i]);
    printf("}\n");
    printf("%d\n",ans);
}
int main()
{
    while(~scanf("%d",&n))
    {
        init();     //因为是直接给予矩阵图的,说明其每点都两辆相连,其实可以不需要初始化
        //建图
        for(int i =1; i <= n ; i++)
            for(int j = 1; j <= n; j++)
        {
            int a;
            scanf("%d",&a);
            if(p[i][j] > a)     //因为每边只给一个值,无需覆盖,所以这个判断可以不要
                p[i][j] = a;
        }
        Prime();
    }
    return 0;
}


kruskal算法
其算法亮点在一直合并最小的边,直到所有点都合并成一个联通块,但在形成一条最小生成树中,碰到两点已经同属一个祖先(即已经联通),那么我们不取这段距离。因为kruskal算法基于边相加的算法,其空间复杂度为(E)。

代码来解释:
小二,换道菜!
好嘞!
这次小二拿的是一样的菜,不过烧法不一样,这次是清蒸的。
案例:

4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0
————————————
28

这里写图片描述

#include<iostream>
#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;
const double PI = acos(-1.0);
const int mod = 1000;
const int N = 10010;
int n,m;

int fa[110];
struct point
{
    int x,y,w;
}ver[N];
bool cmp(point a,point b)
{
    return a.w < b.w;
}
int Find(int x)
{
    if(fa[x] != x)
        fa[x] = Find(fa[x]);
    return fa[x];
}
void kruskal()
{
    int ans = 0;
    sort(ver,ver+m,cmp);
    for(int i = 0; i < m; i++)
    {
        printf("此时最小的边%d点->%d点: %d\n",ver[i].x,ver[i].y,ver[i].w);
        int dx = Find(ver[i].x);
        int dy = Find(ver[i].y);
        printf("%d的father: %d\n",ver[i].x,dx);
        printf("%d的father: %d\n",ver[i].y,dy);
        if(dx != dy)
        {
            printf("加入\n");
            fa[dx] = dy;
            ans += ver[i].w;
        }
    }
    printf("%d\n",ans);
}
int main()
{
    while(~scanf("%d",&n))
    {
        m = 0;
        for(int i = 1; i <= n; i++)
        {
            fa[i] = i;
            for(int j = 1; j <= n; j++)
            {
                int a;
                scanf("%d",&a);
                if(i < j)
                {
                    ver[m].x = i;
                    ver[m].y = j;
                    ver[m].w = a;
                    m++;
                }
            }
        }
        kruskal();
    }
    return 0;
}



小二刚才是试吃,现在可以上菜了!
试吃?好咧。。。-_-你说什么就是什么呗。
一盆花生米来咯
http://poj.org/problem?id=2485
。。。就是模板题,果然便宜货。水过


一盆黄瓜拌蒜
http://acm.hdu.edu.cn/showproblem.php?pid=1102
有点味道,把已经建好的路事先处理就好了。
味道一般,不过还是挺清凉的。
kruskal:

	 int q;
        scanf("%d",&q);
        while(q--)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            int dx = Find(a);
            int dy = Find(b);
            if(dx != dy)
                fa[dx] = dy;
        }

prim:

		int q;
        scanf("%d",&q);
        while(q--)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            p[a][b] = p[b][a] = 0;
        }

下一道
。。。
。。。
等了很久。。。
上了一碗花生米。
http://acm.hdu.edu.cn/showproblem.php?pid=1233
又是模板题哦,虽然不太好吃,还是好好打吧,会打错哦,我还是调试一下发现我打错了个字母wuwu。

小二,怎么回事,要不花生米,要不黄瓜片,再来这些东西,大爷招待的朋友都要走了!!!把这些便宜货一下都拿完呗。
http://acm.hdu.edu.cn/showproblem.php?pid=1162
预处理下点距离。

http://acm.hdu.edu.cn/showproblem.php?pid=1301
题意:题目这么长,其实大部分都在扯逼-_-。以字母代替点,每行输入字母,与其相连的个数,以及相连的具体点和其距离,计算全联通最小距离。
题解:只需要把字母距离化为可用的每两点距离,或邻接矩阵即可。注意:并查集初始化别和输入一起了,因为输入只有n-1次,我就少初始化了wa了一次。不知道在hdu能A的情况下poj就runtime error了。。。


菜来了?
热酱鸭舌
http://poj.org/problem?id=1789
题意较难。。。作为渣渣看了好久。
题意应该是:用一个字符串代表一个编号,这两个编号之间不同字母的个数代表两个编号之间的distance。求的Q为连接所有点的最短距离。预处理下后,套模板。。。


有历史性的红酒(据说是秦国时代的),配上上好的烤鸭!
http://acm.hdu.edu.cn/showproblem.php?pid=4081
X, Y and P ( 0 <= X, Y <= 1000, 0 < P < 100000). (X, Y) is the coordinate of a city and P is the population of that city.
给出坐标和人力,要求A/B最大,其中A表示徐福变的一条路的人口(即指这条路连接的两个城市的人数之和),B表示剩下(n-1)条路的长度。
额,这题有点难。。。枚举删除最小生成树上的每条边,再求最小生成树这肯定超时,时间复杂度度将近O(n^3),查阅了下这种类型叫“次小生成树”,看来需要学习新知识了。
我们把这个拎出来讲吧:
http://blog.youkuaiyun.com/qq_33199236/article/details/52027646


红烧鲤鱼王
http://acm.hdu.edu.cn/showproblem.php?pid=1875
题意:要求在多个岛之间建桥,并实现全畅通。但是有限制桥的长度是10<=x<=1000,桥价格为100元/米。给你小岛坐标,若无法全畅通输出“oh!”,否则输出最小花费。
先预处理各点距离,存入数组用kruskal做,或化邻接矩阵用prim做,但是这里衍生出另一个问题,如何判断无法全畅通,我想只需要记录连接的点的个数,没办法连完n个的即无法全畅通。注意:桥长限制,和浮点化。这里桥长限制最好在预处理的时候就考虑好,
我在第一次做时候,放里面判断,应该会导致dis数组出错。


我最近拉了些题做做吧,我顺便写了个题解, 我过几天贴上去。
这儿就完结了。
http://blog.youkuaiyun.com/qq_33199236/article/details/52088758

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值