第二十一章 Prim算法与Kruskal算法(通俗证明与详细讲解)

news2024/11/24 16:08:09

第二十一章 Prim算法与Kruskal算法

    • 一、最小生成树
    • 二、prim算法
      • 1、算法思路
      • 2、算法模板
        • (1)问题
        • (2)模板
        • (3)分析
        • 4、常见疑惑
          • (1)与dijkstra算法的区别以及循环次数问题:
          • (2)正确性证明:
    • 三、kruskal算法
      • 1、算法用途
      • 2、算法思想
      • 3、正确性证明
        • (1)为什么构成环的边不是最小生成树中的边?
        • (2)为什么不构成环的边就一定是最小生成树的边?
      • 4、代码实现思路
      • 5、模板
        • (1)问题:
        • (2)代码:
        • (3)分析:

一、最小生成树

我们先解释一下什么是最小生成树。

这个概念是基于图的,如果说存在一条路线串通起来了所有的点,那么这条路线就叫做生成树。而在这些路线中最短的那一条就叫做最小生成树。
在这里插入图片描述
如上图所示,图中的红色路线就是一个生成树,假设这条红色路线是众多生成树路线中最小的,那么这个路线就叫做最小生成树。而我们后续所讲解的普利姆算法和克鲁斯卡尔算法就是用来解决最小生成树问题的。

二、prim算法

1、算法思路

我们可以将上述图中的点看作两个集合。其中一个集合st是已经确定最小生成树中的边上的点。另外一个集合是还没有确定是否是最小生成树中的边上的点。

我们每次都将未确定的集合dis中,挑选一个距离st集合最近的边对应的点进入集合st。

我每次都选择距离集合最近的边,那么最终得到的就会是最小的生成树。

那么我们如何判断最短的边呢?

其实很简单,就是比较dis集合中点到st集合中的点的距离。距离最近的那个边,自然就是我们想要的边。

那么我们如何判断不存在最小生成树呢?

如果一个不确定的集合内的点,到已经确定的点的集合最短的距离是正无穷。那么就说明此时不存在最小生成树。

那么负环的存在对最小生成树有影响吗?
答案是没有影响,负环之所以会对之前的算法产生影响,是因为我们可以利用负环无限松弛。但是我们现在的prim算法中不需要松弛。我们只在乎边权的大小。

2、算法模板

(1)问题

在这里插入图片描述

(2)模板

#include<iostream>
#include<cstring>
using namespace std;
const int N=510;
int g[N][N],dis[N];
bool st[N];
int n,m;
int prim()
{
    int ans=0;
    memset(dis,0x3f,sizeof dis);
    dis[1]=0;
    for(int i=0;i<n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
            if(!st[j]&&(t==-1||dis[t]>dis[j]))t=j;
        st[t]=true;
        if(dis[t]==0x3f3f3f3f)return 0x3f3f3f3f;
        ans+=dis[t];
        for(int j=1;j<=n;j++)
            if(!st[j]&&dis[j]>g[t][j])dis[j]=g[t][j];
    }
    return ans;
}
int main()
{
    memset(g,0x3f,sizeof g);
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        if(a!=b)
        g[a][b]=g[b][a]=min(g[a][b],c);
    }
    int res=prim();
    if(res==0x3f3f3f3f)puts("impossible");
    else cout<<res<<endl;
    return 0;
}

(3)分析

在这里插入图片描述

4、常见疑惑

(1)与dijkstra算法的区别以及循环次数问题:

看完这个思路,我想大家应该会想到我们之前讲解的一个算法:dijkstra算法。这个算法和prim算法是很相似的。都是分成了两个集合。但是不同的是,dijkstra算法中的dis数组是每个点到源点的距离,而prim算法中的dis数组是每个点到集合的距离

而每个点到源点的距离最多也就经过n-1条边,所以dijkstra算法循环n-1次。
与此同时,最小生成树包含n个点,而我们要判断每个点到集合的距离,所以prim算法需要循环n次。

但两者都采用了相同的思想:贪心思想

(2)正确性证明:

其实我们的prim算法的核心思想就是dis集合中的点距离st集合内的点最短的边,就是最小生成树路径上的边。

所以我们只需要证明这个观点是正确的。
我们采用反证法的方式去证明:在某次循环中,假设dis数组中存在一个点A其到st集合的点B的距离不是最小的,但是此时的g[a][b]边却是最小生成树的一部分。由于g[a][b]此时不是最小的,那么就必存在一个当前的最小边g[c][d]。如下图所示:
在这里插入图片描述
那么我们假设的最小生成树就应该是红色线对应的路径:S+g[a][b]。但是由于g[a][b]>g[c][d]
所以,S+g[a][b]>S+g[c][d] 。 所以通过AB边构成的最小生成树并不是最小生成树,与刚刚的假设矛盾。
因此,我们的原定理:dis集合中的点距离st集合内的点最短的边,就是最小生成树路径上的边,成立

三、kruskal算法

1、算法用途

我们发现我们刚刚的普利姆算法一般适用于稠密图,因为我们遇到稠密图的时候喜欢用邻接矩阵。
那么假设我们遇到了稀疏图,我们可以选择用prim算法,然后利用优先队列去优化这个算法(和dijkstra算法的优化是一样的),但是这种做法过于麻烦。因此,在这种情况下,我们习惯用的就是相对简单、时间复杂度和优化后的prim算法相同的kruskal算法。

2、算法思想

我们想选择的是最小生成树,也就是说我们想选的是尽可能小的边。好,那么既然这样我不管三七二十一,先将所有的边进行升序排序,然后假设一个st数组,这个数组存储的是最小生成树中的边。接着,我们从小到大遍历所有的边。如果我们遍历的边和当前st数组中的边没有构成环,那么就把这个边放到st数组中,如果构成了环,则说明当前的边不是最小生成树中的边,因此就不把这个边放到st数组中。

3、正确性证明

在开始之前我们需要明确一点:

在排好序的边中,先遍历的边的权重小于后遍历的边的权重。

在明确了这个性质之后,我们先来解决下面的两个问题:

(1)为什么构成环的边不是最小生成树中的边?

我们画出下面的图帮助大家理解:
在这里插入图片描述
我们再加入新的边后,某几个点构成了环,这就说明,在新的边加入之前,我们的这几个点已经被连结成了一串联通块。如果一个环,我们删除任意的一条边,也仅仅是将这个环变成线,但这几个点依旧是连通块。同时,最小生成树的边数是n-1,也就是说我们不能存在环。即,我们必须删除环中的任意一条边。由于我们要的是最小生成树,所以我们一定是删除环中最大的边,即我们新加入的边。

同时,我们删除任意一边,都不会影响这一部分和其他连通块的连接。因为,不管我们删除哪一条边,这几个点都是连通的,并且我们的点数也没变,即和外界相连的边都没变。所以,我们删除环上的边,并不影响环上的点和连通块之外的点的连接。也就是说,我们删除新来的成环的边对其余连通块和本连通块之间的连接不会产生任何的影响。

因此,我们新加入的成环的边一定不是答案。

(2)为什么不构成环的边就一定是最小生成树的边?

首先,不构成环也就是说明本条边的加入,连接了两个之前不相连的连通块。那么现在要解决的问题就是,为什么这次加入的连接两个连通块的边就是连接两个连通块的边中最小的。

从我们一开始说的那个规律:**在排好序的边中,先遍历的边的权重小于后遍历的边的权重。**所以如果后面再次出现一个连接两个连通块的边,那么新出现的边也必定是大于我们现在的边的。因此,当我们出现了一个不构成环的边的时候,这个边必定是在最小生成树里的。

4、代码实现思路

好了。通过我们上面的讲解我们已经了解大体的逻辑,同时大概知道了这个算法的正确性。那么我们纵观整体,其实这就是一个集合的合并问题。而集合的合并这个操作可以利用我们之前学过的一个算法—并查集

如果新遍历的边所对的连通块和遍历之前的连通块的祖先是一样的,这就说明现在我们新遍历的边所在的连通块和我们st数组中的点所在的连通块,就是一个连通块。因此,根据我们刚才的推导,如果我们将已经在一个连通块上的点的新边加进来,就会成环。所以,如果两个连通块的祖先是一样的,则扔掉当前正在遍历的边。

同理,如果我们新加入的边所在的连通块和我们st数组内的边所在的连通块,这二者的祖先是不一样的。说明我们应该加入这个边。所以我们只需要将这个边并入我们的集合,即新边的祖先认爹的过程。

5、模板

(1)问题:

在这里插入图片描述

(2)代码:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=2e5+10,M=3e5+10;
struct edge
{
    int a,b,c;
}edges[N];
int p[N];
int n,m;
int find(int x)
{
    if(p[x]!=x)p[x]=find(p[x]);
    else return p[x];
}
bool cmp(edge a,edge b)
{
    return a.c<b.c;
}
int kruskal()
{
    int cnt=0,nums=0;
    sort(edges,edges+m,cmp);
    for(int i=0;i<m;i++)
    {
        int pa=find(edges[i].a),pb=find(edges[i].b);
        if(pa!=pb)
        {
            p[pb]=pa;
            cnt+=edges[i].c;
            nums++;
        }
        if(nums==n-1)return cnt;
    }
    return 0x3f3f3f3f;
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        if(x!=y)
        edges[i]={x,y,z};
    }
    for(int i=1;i<=n;i++)
        p[i]=i;
    int res=kruskal();
    if(res!=0x3f3f3f3f)cout<<res<<endl;
    else puts("impossible");
    return 0;
}

(3)分析:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/89552.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASEMI肖特基二极管MBR20200FCT特征,MBR20200FCT应用

编辑-Z ASEMI肖特基二极管MBR20200FCT参数&#xff1a; 型号&#xff1a;MBR20200FCT 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;200V 最大RMS电桥输入电压&#xff08;VRMS&#xff09;&#xff1a;140V 最大直流阻断电压&#xff08;VDC&#xff09…

Visual Studio配置c环境

Visual Studio配置c环境 Visual Studio配置c环境 1 下载Visual Studio 下载Visual Studio软件可以直接在其内进行c的运行&#xff0c;不需要配置。官网&#xff0c;其中社区版免费。 2 安装Visual Studio 2.1 VS把我们想使用到的开发语言和应用都已经归类好&#xff0c;我们…

Python学习基础笔记四十九——类的命名空间

1、创建一个类就创建了一个类的名称空间&#xff0c;用来存储类中定义的所有名字&#xff0c;这些名字称为类的属性。而类中可以定义两种属性&#xff1a; 静态属性&#xff1a;就是直接在类中定义的变量。 动态属性&#xff1a;就是定义在类中的方法。 class Course:langua…

直播基本流程【推流-播流-流媒体服务器】

直播基本流程 这里不涉及到业务相关 &#xff0c; 这里简要说明直播流程 推流端&#xff1a; 负责将本地的音视频数据推送至流媒体服务器 流程&#xff1a;音视频数据采集->编码->封装->协议封包 功能&#xff1a;美颜滤镜、音效处理、回音消除 播流端&#xff1…

SpringBoot2核心技术(核心功能)- 04、配置文件【4.1 yaml的用法 + 4.2 自定义类绑定的配置提示】

核心功能概览 1、文件类型 1.1、properties 同以前的properties用法 1.2、yaml 1.2.1、简介 YAML 是 “YAML Ain’t Markup Language”&#xff08;YAML 不是一种标记语言&#xff09;的递归缩写。在开发的这种语言时&#xff0c;YAML 的意思其实是&#xff1a;“Yet An…

猿如意中的【editorconfig-222.2889.3】工具详情介绍

一、工具名称 editorconfig-222.2889.3 二、下载安装渠道 editorconfig-222.2889.3 通过CSDN官方开发的【猿如意】客户端进行下载安装。 2.1 什么是猿如意&#xff1f; 猿如意是一款面向开发者的辅助开发工具箱&#xff0c;包含了效率工具、开发工具下载&#xff0c;教程文…

《Fluent Python》笔记 | 协程

生成器作为协程 协程是指一个过程&#xff0c; 这个过程与调用方协作&#xff0c; 产出由调用方提供的值。 协程使用的简单演示&#xff08;用作协程的生成器&#xff09;&#xff1a; >>> def simple_coroutine(): # 生成器函数 ... print(-> coroutine starte…

JavaScript系列之通过babel体验ES6模块化

文章の目录一、创建项目文件夹二、打开cmd窗口三、初始化项目四、安装依赖模块五、项目根目录创建文件六、在babel.config.js 文件中添加如下配置七、编写代码八、执行代码九、相关项目依赖写在最后一、创建项目文件夹 名称不要使用中文&#xff0c;不能使用 babel&#xff0c…

[附源码]计算机毕业设计的旅游景点管理系统的设计与实现Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis MavenVue等等组成&#xff0c;B/S模式…

VS Code —— 介绍如何配置快捷代码片段和一些自用插件

VS Code —— 介绍如何配置快捷代码片段和一些自用插件 《工欲善其事&#xff0c;必先利其器》—— 既然点进来了&#xff0c;麻烦你看下去&#xff0c;希望你有不一样的收获~ 一、配置代码片段 打开 VS Code&#xff0c;输入快捷键 Ctrl Shift p&#xff0c;打开面板&#…

继真人秀后的又一次大赛,万应低代码一路向前

12月8日&#xff0c;凛冬的长沙&#xff0c;比赛现场暖气充足&#xff0c;11 个参赛团队的队长正在台下跃跃欲试&#xff0c;本届“万应杯”低代码应用开发大赛已经开启月余&#xff0c;大家都很期待能在淘汰赛上一展身手。 他们手上的项目&#xff0c;涉及到建筑、园区、生鲜…

30、基于51单片机的数字电压表(ADC0809)(Proteus仿真+程序)

编号&#xff1a;30 基于51单片机的数字电压表&#xff08;ADC0809&#xff09; 功能描述&#xff1a; 本设计由51单片机最小系统ADC0809模块八路路模拟量输入模块12864显示模块 1、主控制器是89C52单片机 2、ADC0809模数转换器进行A/D转换&#xff0c;读取电压八路数据&…

现在转行码农的成本已经非常高了,别盲目转行..

转行码农一直是个比较火热的话题&#xff0c;也有很多读者咨询过这个问题&#xff0c;转成功的也不少&#xff0c;比如下面这位香港的同学&#xff1a; 这位朋友半年前就跟我聊过&#xff0c;他不太想干没有技术含量的体力活&#xff0c;一直在坚持自学&#xff0c;这也算如愿…

软件测试基础知识总结(面试临时抱佛脚)

之前有将基础的软件测试知识做了一个总结&#xff0c;但比较潦草&#xff0c;很多内容只是一笔带过&#xff0c;快到年底了&#xff0c;自己也有个写年终知识总结文档的计划&#xff0c;就将基础的理论知识重新整理一番。。。 有人问我&#xff0c;这些都是能搜索到的知识&…

65-82-springcloud-gateway-config-bus

65-82-springcloud-gateway-config-bus&#xff1a; Gateway gateway官网&#xff1a;https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/ 1、什么是gateway Gateway是在Spring生态系统之上构建的API网关服务&#xff0c;基…

c++引用

1.什么是c引用&#xff1f; 引用是c对c的重要扩充。c中新增了引用的概念&#xff0c;引用可以作为一个已定义变量的别名。 #include "stdafx.h" #include <iostream> using namespace std; // 1.引用的基本使用 void test01(){int a 10;// 给变量a取一个别名…

金仓数据库KingbaseES 归档日志清理

WAL是Write Ahead Log的简写&#xff0c;和Oracle的redo日志类似&#xff0c;在R3版本存放在data/sys_log中&#xff0c;R6版本以后在data/sys_wal目录&#xff0c;在数据库访问过程中&#xff0c;任何对数据块的修改都会记录到wal日志&#xff0c;并写入到wal文件保存到磁盘&a…

PMP有没有必要续证呢?

在还只看到标题的时候&#xff0c;我当时就觉得必须续啊&#xff0c;为什么不续&#xff0c;我花了那么多时间精力和钱财去考的&#xff0c;我自然得去给它续上&#xff0c;不然白拿了&#xff0c;才拿了三年我还没捂热就给我失效了多不值。 首先美国PMI要求PMP证书是三年一换…

面试题 :Unity编辑器基础

1、请描述游戏动画有几种&#xff0c;以及其原理。 关键帧动画&#xff1a;每一帧动画序列当中包含了顶点的空间位置信息以及改变量&#xff0c;然后通过插值运算&#xff0c;得出动画效果。选中某一游戏对象&#xff0c;创建animation&#xff0c;添加属性Transform&#xff0…

【Meetup 预告】OpenMLDB + MaxCompute:集成打通云上生态,高效构建 AI 应用

2022年12月3日&#xff08;周六&#xff09;上午10&#xff1a;00-12:00&#xff0c;开源机器学习数据库 OpenMLDB 第八期 Meetup 将通过线上直播的形式展开。 活动背景 数据的爆发式增长为 AI 应用的繁荣提供了坚实的基础&#xff0c;而云服务作为新一代快速整合、高效计算的…