【数据结构】六、图:5.图的最小生成树MST(普里姆(Prim)算法、克鲁斯卡尔(Kruskal)算法、Boruvka 算法)

news2024/9/24 15:19:56

2.最小生成树MST

文章目录

    • 2.最小生成树MST
      • 2.1 普里姆(Prim)算法
        • 算法思路
      • 2.2 克鲁斯卡尔(Kruskal)算法
        • 算法思路
      • 2.3 Boruvka 算法
        • 2.3.1基本原理
        • 2.3.2基本过程

一个图可以有多个生成树,我们定义无向连通图的 最小生成树(Minimum Spanning Tree,MST)为 边权和最小的生成树。

【注意】

  1. 最小生成树也可能有多个。
  2. 一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点。
    • 如果一个连通图本身就是一棵树,则其最小生成树就是它本身。
  3. 只有连通图才有生成树,而对于非连通图,只存在生成森林。

构造最小生成树有多种算法,但大多数算法都利用了最小生成树的下列性质:

假设G=(V,E)是个带权连通无向图,U是顶点集V的一个非空子集(U∈V)。若(u,v)是一条具有最小权值的边,其中u∈U, v∈V-U,则必存在一棵包含边(u,v)的最小生成树。

基于该性质的最小生成树算法主要有Prim算法和Kruskal算法,它们都基于贪心算法的策略。下面介绍一个通用的最小生成树算法:

GENERIC_MST(G){
	T=NULL;
	while T 未形成一棵生成树;
		do 找到一条最小代价边(u, v)并且加入T后不会产生回路;
			T=T U (u, v);
}

通用算法每次加入一条边以逐渐形成一棵生成树,下面介绍两种实现上述通用算法的途径。

2.1 普里姆(Prim)算法

从一个顶点开始,每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止。

prim

  • Prim算法只和顶点个数有关系,它的时间复杂度是O(|V|2)
  • 适合于边稠密的图

算法思路

创建两个数组isJoin标记各节点是否已经加入树,lowCost标记各节点加入树的最低代价。

初始化:把第一个结点v0的isJoin除了它自己全部标记为false,lowCost填入边的权值(不相连为∞)。

找出 lowCost 最小的结点v1加入(isJoin = true),这个时候,初始结点v0和这个新结点v1就是一个树,那么 lowCost 保存的是整棵树的的最低代价,所以需要遍历新加入的结点v1的边的权值,当初始节点v0的 lowCost 大于新结点v1的 lowCost ,则直接更新 lowCost 为那个更小的权值。

再在 lowCost 中寻找权值最小的结点,然后加入树,然后遍历它的 lowCost 进行替换。直到所有结点都加入生成树。

也就是每个结点都遍历一遍lowCost,所以时间复杂度为O(n2)。

初始化:

然后进行第一轮low的遍历

在这里插入图片描述

修改lowCost之后,修改isJoin:

在这里插入图片描述

在这里插入图片描述

然后这个点就结束了,再去寻找下一个lowCost最低的点:

在这里插入图片描述

…一直扫描所有点,直到isJoin全部为true:

在这里插入图片描述

在邻接矩阵中:

// 最小生成树MST - Prim算法
// 贪心, O(n^2), 适用于稠密图
void MiniSpanTree_Prim(MGraph G){
	int i, j;
	int v, min; 			//min是最小权值,v是最小权值的下标
	int adjvex[G.vexnum];	//保存相关顶点下标
	int lowCost[G.vexnum];	//保存标记各节点加入树的最低代价

//初始化
	lowCost[0] = 0;	//初始化第一个权值为0,即v0加入生成树
	//lowCost的值为0,在这里就是此下标的顶点已经加入生成树
	adjvex[0] = 0;	//初始化第一个顶点下标为0

	for(i=0; i<G.vexnum; i++){
		lowCost[i] = G.Edge[0][i];	//将v0顶点与之组成边的权值存入数组
		adjvex[i] = 0;	//初始化都为v0的下标
	}

//寻找
	for(i=1; i<G.vexnum; i++){
		min = INFINITY;	//初始化最小权值为∞,通常设置一个不可能的很大的数字
		j = 1;	//0已经初始化,从1开始
		v = 0;

		//循环全部顶点找最小权值
		while(j < G.vexnum){
			//如果权值不为0且权值小于min
			if(lowCost[j]!=0 && lowCost[j]<min){
				min = lowCost[j];	//则让当前权值成为最小值
				v = j;	//将当前最小值的下标存入k
			}
			j++;
		}

		printf("(%d, %d)", adjvex[v], v);	//打印当前顶点边中权值的最小边

		for(j=1; j<G.vexnum; j++){ //修改lowCost数组
			//若下标为v顶点各边权值小于此前这些顶点未被加入生成树权值
			if(lowCost[j]!=0 && G.Edge[v][j] < lowCost[j]){
				lowCost[j] = G.Edge[v][j];	//将较小权值存入lowCost
				adjvex[j] = v;	//将下标为v的顶点存入adjvex
			}
		}
	}//for
}

由算法代码中的循环嵌套可得知此算法的时间复杂度为O(n2)。

2.2 克鲁斯卡尔(Kruskal)算法

与Prim算法从顶点开始扩展最小生成树不同,Kruskal 算法是一种按权值的递增次序选择合适的边来构造最小生成树的方法。

这个算法不选择一开始的顶点,直接找权值最小的边,从小到大加入边,是个贪心算法。

Kruskal

  • Kruskal算法只关系边的个数,它的时间复杂度是O(|E|log2|E|)
  • 适合于边稀疏的图

算法思路

因为 kruskal 算法每次找的是权值最小的边,所以可以预处理把所有的边进行排序。用 weight, V1, V2 保存这样一个边的信息,weight是权值,V1, V2是它连接的两个结点。

一开始,每一个结点都可以看作不同的集合。当一个边的权值足够小,并且两个结点V1, V2属于不同的集合,那么这时候就可以把两个结点连起来(选择这条边)构成一个新的集合。

一直从小到大遍历完所有的边。

在这里插入图片描述

算法虽简单,但需要相应的数据结构来支持……具体来说,维护一个森林,查询两个结点是否在同一棵树中,连接两棵树。

抽象一点地说,维护一堆 集合,查询两个元素是否属于同一集合,合并两个集合。

其中,查询两点是否连通和连接两点可以使用并查集维护。

如果使用 O(mlog m) 的排序算法,并且使用 O(mα(m,n)) 或 O(mlog m) 的并查集,就可以得到时间复杂度为 O(mlog m) 的 Kruskal 算法。


于是Kruskal算法代码如下:

/*Kruskar算法生成最小生成树*/
void MiniSpanTree_Kruskal(MGraph G){
	int i, n, m;
	Edge edges[MAXEDGE];	//定义边集数组
	int parent[MAXVEX];	//定义一数组用来判断边与边是否形成环路
	/*此处省略将邻接矩阵G转化为边集数组edges并按照权由小到大排序的代码*/
	for(i=0; i<G.numVertexes; i++){
		parent[i] = 0;	//初始化数组为0
	}
	for(i=0; i<G.numVertexes; i++){
		n = Find(parent, edges[i].begin);
		m = Find(parent, edge[i],end);
		//假如n与m不等,说明此边没有与现有生成树形成环路
		if(n != m){
		//将此边的结尾顶点放入下标为起点的parent中表示此顶点已经在生成树集合中
            parent[n] = m;
            printf("(%d, %d, %d)", edges[i].begin, edges[i].end, edges[i].weight);
		}
	}
}

/*查找连线顶点的尾部下标*/
int Find(int *parent, int f){
	while(parent[f] > 0){
		f = parent[f];
	}
	return f;
}

此算法的Find函数由边数n决定,时间复杂度为O(logn),而外面有一个for循环n次。所以克鲁斯卡尔算法的时间复杂度为O(nlogn)。


对比两个算法,克鲁斯卡尔算法主要是针对边来展开,边数少时效率会非常高,所以对于稀疏图有很大的优势;而普里姆算法对于稠密图,即边数非常多的情况会更好一些。

2.3 Boruvka 算法

考研不考

很容易发现,对于某些毒瘤的问题,边的数量极其大,而边集内部又存在各种规律可能需要套上各种数据结构加以优化。但是此时Kruskal和Prim并不能很好的嵌合进这些数据结构。此时我们可以引入Boruvka算法。

该算法的思想是前两种算法的结合。它可以用于求解无向图的最小生成森林。(无向连通图就是最小生成树。)在边具有较多特殊性质的问题中,Boruvka 算法具有优势。例如 CF888G 的完全图问题。

对于Boruvka算法,一个比较笼统的表述是,一个多路增广版本的Kruskal。

2.3.1基本原理

在并查集算法中,初始状态下我们将每个点视为一个独立的点集,并不断地合并集合。

在Brouvka算法中,我们在一开始将所有点视为独立子集,每次我们找到两个集合(即为连通块)之间的最短边,然后扩展连通块进行合并。不断扩大集合(连通块)直到所有点合并为一个集合(连通块)

可以发现,Boruvka算法将求解最小生成树的问题分解为求连通块间最小边的问题。它的基本思想是:生成树中所有顶点必然是连通的,所以两个不相交集必须连接起来才能构成生成树,而且所选择的连接边的权重必须最小,才能得到最小生成树。

通过一张动态图来举一个例子:

eg

2.3.2基本过程
  1. 首先将所有点视为各自独立的集合,初始化一个空的MST;
  2. 当子集个数大于1的时候,对各个子集和执行以下操作:
    1. 找到与当前集合有边的集合,选出权值最小的边;
    2. 如果该权值最小的边不在MST中;
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
const int M = 1e6 + 10;

struct node { int u, v, w; } edge[M];

int f[N], best[N];
bool vis[N];
int n, m;

int find(int x){
    return f[x] == x ? x : find(f[x]);
}

inline const bool cmp(int u, int v){
    if(v == 0) return 1;
    if(edge[u].w != edge[v].w) return edge[u].w < edge[v].w;
    return u < v;
}

inline void init(){
    cin >> n >> m;
    for(int i = 1; i <= m; i++) cin >> edge[i].u >> edge[i].v >> edge[i].w;
    for(int i = 1; i <= n; i++) f[i] = i;
}

inline int boruvka(){
    memset(vis, 0, sizeof(vis));
    int ans = 0, cnt = 0;
    bool status = true;
    while(true){
        status = false;
        //遍历边集
        for(int i = 1; i <= m; i++){
            if(!vis[i]){
                int uu = find(edge[i].u), vv = find(edge[i].v);
                if(uu == vv) continue;
                if(cmp(i, best[uu])) best[uu] = i;
                if(cmp(i, best[vv])) best[vv] = i; 
            }
        }
        //遍历点集
        for(int i = 1; i <= n; i++){
            if(best[i] && !vis[best[i]]){
                status = true, cnt++, ans += edge[best[i]].w;
                vis[best[i]] = 1;
                int uu = find(edge[best[i]].u), vv = find(edge[best[i]].v);
                f[uu] = vv;
            }
        }
    }
    if(cnt == n - 1) return ans;
    return -1;
}

signed main(){
    init();
    boruvka();
    return 0;
}

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

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

相关文章

PHP餐厅点餐系统小程序源码

&#x1f37d;️【餐厅点餐新纪元&#xff0c;点餐系统让用餐更便捷&#xff01;】&#x1f4f1; &#x1f50d; 一键浏览&#xff0c;菜单尽在掌握 &#x1f4f1; 走进餐厅&#xff0c;无需再担心找不到服务员或菜单被抢光&#xff01;餐厅点餐系统让你轻松扫描桌上的二维码…

机器学习笔记:门控循环单元的建立

目录 介绍 结构 模型原理 重置门与更新门 候选隐状态 输出隐状态 模型实现 引入数据 初始化参数 定义模型 训练与预测 简洁实现GRU 思考 介绍 门控循环单元&#xff08;Gated Recurrent Unit&#xff0c;简称GRU&#xff09;是循环神经网络一种较为复杂的构成形式…

轻量级的灰度配置平台|得物技术

一、前言 随着近几年得物的业务和技术的快速发展&#xff0c;我们不管是在面向C端场景还是B端供应链&#xff1b;业务版本的迭代更新&#xff0c;技术架构的不断升级&#xff1b;不管是业务稳定性还是架构稳定性&#xff0c;业务灰度的能力对我们来说都是一项重要的技术保障&a…

x264 编码器 PSNR算法源码分析

PSNR PSNR(Peak Signal-to-Noise Ratio,峰值信噪比)是一种常用的图像质量评价指标,用于衡量图像或视频的清晰度和质量。PSNR是基于信号的最大可能功率与影响信号的噪声功率之间的比率。在图像处理领域,PSNR通常用来评估图像压缩或图像增强算法的效果。 PSNR的计算公式是…

思科CCNP最新考证流程

CCNP CCNP全称思科网络高级工程师认证&#xff08;Cisco Certified Network Professional&#xff09;&#xff0c;是Cisco思科认证中的中级认证。获得ccnp证书表示着资深网络工程师具有对100个节点到超过500个节点的融合局域网和广域网进行安装、配置和故障排除的能力。能够管…

LeetCode257 二叉树的所有路径

前言 题目&#xff1a; 257. 二叉树的所有路径 文档&#xff1a; 代码随想录——二叉树的所有路径 编程语言&#xff1a; C 解题状态&#xff1a; 没思路&#xff0c;简单题强度好高… 思路 本题利用了递归加回溯的思路。 这道题目要求从根节点到叶子的路径&#xff0c;所以需…

一个Indie Hacker的微SaaS技术栈

如今&#xff0c;可用的技术非常多&#xff0c;我们每个月都会看到各种新的 JS 框架发布&#xff0c;有时&#xff0c;如果你一开始没有选择正确的技术堆栈&#xff0c;以后扩展起来就会很困难。因此&#xff0c;在今天的文章中&#xff0c;我将与你分享我用于开发微型 SaaS 的…

vue使用富文本编辑器+自由伸缩图片

首先要下载依赖&#xff0c;下方是本人使用的package.json&#xff0c;下载完依赖如果有启动项目失败的情况&#xff0c;建议将依赖版本降低或使用和下方一样的版本 package.json代码 {"name": "l","version": "0.1.0","privat…

Linux中线程常用接口(创建,等待,退出,取消)

pthread_create #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); Compile and link with -pthread. 编译时应注意。 #include<iostream> #in…

使用Playwright解决reCAPTCHA的分步指南

您是否在您的网络爬虫中遇到过CAPTCHA&#xff1f;许多网站使用CAPTCHA系统&#xff08;最常见的是reCAPTCHA&#xff09;来防止自动化访问。但是&#xff0c;本文将指导您使用Playwright&#xff08;一种强大的浏览器自动化工具&#xff09;和CapSolver&#xff08;一个设计用…

# 利刃出鞘_Tomcat 核心原理解析(二)

利刃出鞘_Tomcat 核心原理解析&#xff08;二&#xff09; 一、 Tomcat专题 - Tomcat架构 - HTTP工作流程 1、Http 工作原理 HTTP 协议&#xff1a;是浏览器与服务器之间的数据传送协议。作为应用层协议&#xff0c;HTTP 是基于 TCP/IP 协议来传递数据的&#xff08;HTML文件…

AI 的偏见来自数据集,而数据集的偏见来自人类 | Open AGI Forum

作者 | Annie Xu 采访、责编 | Eric Wang 出品丨GOSIM 开源创新汇 Richard Vencu&#xff0c;现任 Stability AI 机器学习运维负责人、LAION 工程负责人兼创始人&#xff0c;他的人生可谓十分精彩。 已过知天命之年的他是个中国通&#xff0c;极其热爱中国的武术、茶叶、诱人…

BugKu CTF Misc:被勒索了 disordered_zip simple MQTT 请攻击这个压缩包

前言 BugKu是一个由乌云知识库&#xff08;wooyun.org&#xff09;推出的在线漏洞靶场。乌云知识库是一个致力于收集、整理和分享互联网安全漏洞信息的社区平台。 BugKu旨在提供一个实践和学习网络安全的平台&#xff0c;供安全爱好者和渗透测试人员进行挑战和练习。它包含了…

03. 剑指offer刷题-二叉树篇(第二部分)

class Solution { public:TreeNode* Convert(TreeNode* pRootOfTree) {if(pRootOfTree nullptr) return nullptr;vector<TreeNode*> cur traversal(pRootOfTree);return cur[0];}// 这道题需要用到「分解问题」的思维&#xff0c;想把整棵链表&#xff0c;可以先把左右…

[upload]-做题笔记

项目下载地址&#xff1a;https://github.com/c0ny1/upload-labs 第一关 查看源代码&#xff0c;可以看到是前端js限制上传jpg,png,gif后缀文件 function checkFile() {var file document.getElementsByName(upload_file)[0].value;if (file null || file "") …

Unity读取Android外部文件

最近近到个小需求,需要读Android件夹中的图片.在这里做一个记录. 首先读写部分,这里以图片为例子: 一读写部分 写入部分: 需要注意的是因为只有这个地址支持外部读写,所以这里用到的地址都以 :Application.persistentDataPath为地址起始. private Texture2D __CaptureCamera…

促进服务消费高质量发展虽好,但不能缺钱

近日&#xff0c;国务院印发《关于促进服务消费高质量发展的意见》&#xff0c;提出6方面20项重点任务。 百度图片&#xff1a;2024讲党课ppt国务院关于促进服务消费高质量发展​ 一是挖掘餐饮住宿、家政服务、养老托育等基础型消费潜力&#xff1b; 二是激发文化娱乐、旅游、…

Upload 上传图标不显示

el-upload如果在使用 Element UI 的 <el-upload> 组件时上传图标不显示&#xff0c;可能是由几个不同的原因造成的。以下是一些排查和解决这个问题的步骤&#xff1a; 如果在使用 Element UI 的 <el-upload> 组件时上传图标不显示&#xff0c;可能是由几个不同的原…

antd react echarts地图组件及使用

地图组件&#xff1a; import { useRef, useEffect } from "react"; import * as echarts from "echarts"; import chinaJson from ./chinaJson;const MapIndex ({option,width "100%",height "100%", }) > {const ref useRef…

08:【stm32】中断二:EXTI(外部中断)

EXTI&#xff08;外部中断&#xff09; 1、EXTI简介2、EXTI的内部结构2.1、EXTI通道2.2、内部寄存器 3、EXTI的编写程序3.1、EXTI的编程接口3.1.1、EXTI_Init 4、编写实验 1、EXTI简介 外部中断控制器&#xff0c;能够检测外部输入信号的变化边沿并由此产生中断。通过检测上升沿…