图论(三)(最小生成树)

news2025/3/18 23:48:54

 一、图的表示(简要概述)

        对于图G=(V,E)( V 为节点的集合,E 为边的集合 = V*V 的子集)有两种表示方法:邻接链表和邻接矩阵,两种表示方法既可以表示有向图,也可以表示无向图。

        如果 G 是连通图,则 E \geq V-1,一个图连通,至少有 v -1 条边。

        邻接链表适合表示稀疏图(边的条数 E 远远小于 V^{2} ),而邻接矩阵适合表示稠密图 (E 接近 V^{2},此外要判V^{2}断两个节点之间是否有边相邻,可以快速通过邻接矩阵判断。

         1.邻接矩阵

                邻接矩阵的表示,考虑节点集合 V=\left \{ 1,2,3......n \right \} ,用一个二维数组定义邻接矩阵A\left [ 1.....n \right ]\left [ 1.....n \right ],满足以下

对于一个简单的有向图(或无向图),邻接矩阵如下:

    无向图:若 u 与 v 之间存在一条边,则 A[u][v]=A[v][u]=1 (两个方向)

    有向图:若有一条 u 指向 v 的边,则 A[u][v]=1;若有一条 v 指向 u 的边,则 A[v][u]=1(单向)

       邻接矩阵的空间消耗为O(V^{2}),无向图的邻接矩阵为对称矩阵。在某些情况下,只存储邻接矩阵的对角线及以上的部分,这样,图占用的存储空间可以减少一半。

        2.邻接链表

        Adj为一个包含 V 条链表的数组,每个节点有一个链表,对于每个节点u∈V,邻接链表Adj[u]包含所有与节点u之间有边相连的节点v。

        如果G是一个有向图,则对于边(u,v)而言,节点 v 将出现在链表 Adj [ u ]里,所有邻接链表的长度之和等于 E;如果G是一个无向图,对于边(u,v),节点v将出现在链表 Adj [ u ] 里,节点 u 将出现在链表 Adj [ v ]里,所有邻接链表的长度之和为 2*E。

        邻接链表表示法的储存空间均为 O(V+E)

二、最小生成树

        1.相关概念

        树的概念:如果一个无向连通图不包含回路(连通图中不存在环),那么就是一棵树 

        最小生成树:一个图中可能存在多条相连的边,一定可以从图中挑出一些边生成一棵树, 当每条边都存在权重时,这时候我们从图中生成一棵树(仅有n-1条边相连n个节点),生成这棵树的总代价就是每条边的权重相加之和。

        一个有 n 个点的图,边一定是 ≥ n-1,最小生成树就是在这些边中选择 n-1 条边,连接 n 个点,这 n-1 条边的边权之和是所有方案里最小的。

        2.最小生成树的应用

        要在 n 个城市之间铺设光缆,主要是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此是要使铺设光缆的总费用最低。需要找到带权的最小生成树

        3.实现最小生成树的算法

        ① prim 算法

        算法思路:

        a.选择一个起始点作为最小生成树的起点,将该起始节点加入最小生成树的集合,并标记已访问。

        b.在所有与最小生成树集合相邻的边中,选择权重最小的边和它连接的未访问节点,并将其加入最小生成树集合,并标记已访问,

        c.重复上述 b 操作,直到所有的节点都加入到最小生成树里面结束。

        算法复杂度分析: 需要考虑每个插入每个节点,并且找出当前最小生成树集合相邻边权重最小和其连接的未访问节点,因此时间复杂度为 O(n^{2})

图示:

代码运行:

// prim 算法求最小生成树 
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=505;
int a[maxn][maxn];
int vis[maxn],dist[maxn];
int n,m;
int u,v,w;
long long sum=0;
int prim(int pos) 
{
	dist[pos]=0;    // dist[x]为当前到达节点 x 的最小权值的边(最小生成树集合中)
	// 一共有 n 个点,就需要 遍历 n 次,每次寻找一个权值最小的点,记录其下标
	for(int i=1;i<=n;i++) 
    {
		int cur=0;
		for(int j=1;j<=n;j++)
			if(!vis[j] && dist[j]<dist[cur]) //找出与最小生成树集合相邻的权重最小的边
				cur=j; 
	    sum+=dist[cur];
	    vis[cur]=1;
	    for(int k=1;k<=n;k++) 
         {
		   // 只更新还没有找到的最小权值
		    if(!vis[k]) dist[k]=min(dist[k],a[cur][k]);
	    }
	}
	return sum;
}
	
int main(void) 
{
	scanf("%d%d",&n,&m);
	memset(a,0x3f,sizeof(a));
	memset(dist,0x3f,sizeof(dist));
	for(int i=1;i<=m;i++) 
    {
		scanf("%d%d%d",&u,&v,&w);
		a[u][v]=min(a[u][v],w);
		a[v][u]=min(a[v][u],w);
	}
	int value=prim(1);
	printf("%lld\n",sum);
	return 0;
} 

prim算法的优化:

        在寻找最小生成树集合相邻边的最小权重时,可以通过优先队列的方法直接进行求出,按照从小到大的顺序直接得出。

        定义一个优先队列,队列中元素记录了节点的编号和与树中顶点相连的边权,将原点压入队列中,然后弹出。执行以下操作:

        从栈顶弹出节点(按照边权排序,最短边权对应的元素弹出),判断该节点是否被访问,若没有被访问,则直接将该点加入最小生成树集合中。并且将与 u 相连的边加入队列当中。

        实现优化后的时间复杂度为 O(nlogn)

void prim(int s)
{
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	dis[s]=0;
	q.push((node){0,s});   // 加入优先队列
	while(!q.empty())
	{
		int u=q.top().v;
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		sum+=dis[u];
		for(int i=0;i<map[u].size();i++)
		{
			int v=map[u][i].v;
			int w=map[u][i].w;
			if(dis[v]>w)
			{
				dis[v]=w;
				q.push((node){w,v});
			}
		}
	}
}

        ② Kruskal 算法

        算法思路: 按照边的权值从小到大排序,选择 n-1 条边,并且保证这 n-1 条边连接 n 个顶点

                如何保证判断某条边是多余的呢?(第 i 条边连接的两个顶点 u v在之前是连通的,该条边就不需要了)

        通过并查集的思想,如果这两个顶点有共同的“祖先”,则此条边多余。                                                                             如果这两个顶点祖先不相同,则将两个顶点合并。

       当插入的节点个数为 n 时,即已经产生了最小生成树。

 void Kruskal()
{
    sort(ed+1,ed+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        int p=FindSet(ed[i].u);
        int q=FindSet(ed[i].v);
        if(p!=q)
        {
            UnionSet(p,q);
            mst+=ed[i].bq;
            if(si[q]==n)
                return;
        }
    }
}

算法时间复杂度的分析:

        对边进行排序: O(ElgE)=O(ElogV) \, \, \, \, \, \, \, \, \, \, E\leq V^{2}

        插入操作执行次数:O(V)

        并查集 FIND-SET 次数:O(E)

        并查集 UNION 次数:O(V)

         

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

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

相关文章

输入输出(3)——C++的标准输入流

目录 一、cin 流 二、成员函数 get 获取一个字符 (一)无参数的get函数。 (二)有一个参数的get函数。 (三&#xff09;有3个参数的get函数 (四&#xff09;用成员函数 getline 函数读取一行字符 (五&#xff09;用成员函数 read 读取一串字符 (六&#xff09;istream 类…

[机缘参悟-187] - 《道家-水木然人间清醒1》读书笔记 - 真相本质 -10- 关系界限 - 一个人只有放下自我,才能看清世界的真相

目录 一、现实生活中&#xff0c;每个人都是盲人摸象 二、一个人认知的本质是神经网络的模型训练 三、每个人的认知具有局限 四、放下自我&#xff0c;就是跳出自我的认知局限 五、站在上帝的视角&#xff0c;俯瞰不同众生的千差万别的大脑认知系统 六、个体的独特性&…

汇编实现的操作系统

掌握X86汇编语言和GDB程序调试工具对于程序员来说是非常重要的_gdb 查看x86汇编-CSDN博客 掌握编译器和虚拟机的开发有哪些方面的好处-CSDN博客 Ville Mikael Turjanmaan开发的一个操作系统MenuetOS可运行在IA-32, x86-64平台上&#xff0c;完全用 64 位汇编语言编写。功能包…

三、ESP32-IDF之LED

实现 ESP32-S3 的 IO 作为输出功能&#xff0c;实现LED灯以500毫秒闪烁一次 1、GPIO&LED简介 1.1、GPIO简介 GPIO 是负责控制或采集外部器件信息的外设&#xff0c;主要负责输入输出功能。 1.2、LED简介 LED&#xff0c;即发光二极管。 2、硬件设计 (1)原理图 LED 接…

模块化程序设计(函数的定义、调用、参数传递、局部变量、全局变量)

函数的引入&#xff1a; 我们曾经学习了程序设计中的三种基本控制结构&#xff08;顺序、分支、循环&#xff09;。用它们可以组成任何程序。但在应用中&#xff0c;还经常用到子程序结构。 通常&#xff0c;在程序设计中&#xff0c;我们会发现一些程序段在程序的不同地方反复…

[数据集][目标检测]痤疮检测数据集VOC+YOLO格式915张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;915 标注数量(xml文件个数)&#xff1a;915 标注数量(txt文件个数)&#xff1a;915 标注类别…

qmt量化教程4----订阅全推数据

文章链接 qmt量化教程4----订阅全推数据 (qq.com) 上次写了订阅单股数据的教程 量化教程3---miniqmt当作第三方库设置&#xff0c;提供源代码 全推就主动推送&#xff0c;当行情有变化就会触发回调函数&#xff0c;推送实时数据&#xff0c;可以理解为数据驱动类型&#xff0…

有趣的css - 圆形背景动效多选框

大家好&#xff0c;我是 Just&#xff0c;这里是「设计师工作日常」&#xff0c;今天分享的是用 css 实现一个圆形背景动效多选框&#xff0c;适用提醒用户勾选场景&#xff0c;突出多选框选项&#xff0c;可以有效增加用户识别度。 最新文章通过公众号「设计师工作日常」发布…

【完整解析】2024电工杯数学建模A题论文与代码

园区微电网风光储协调优化配置 1 论文2 代码分享2.1 第三题第一问 3 数据与代码 1 论文 2 代码分享 2.1 第三题第一问 function anssq3w1ObjFun(ttt,id); %ttttt(1); tt[750,0,0,1000,600,500]; limttt(1)*200; limmttt(2)*500*0.9-ttt(2)*500*0.1; t1ttt(3)*1000;t2ttt(4)*1…

【spring】@RequestMapping注解学习

RequestMapping介绍 官网地址&#xff1a;Mapping Requests :: Spring Framework RequestMapping 是Spring框架中的一个核心注解&#xff0c;主要用于处理HTTP请求的地址映射。它属于Spring MVC框架的一部分&#xff0c;用于将接收到的Web请求映射到特定的处理器类或处理器方…

MySQL存储过程for循环处理查询结果

在MySQL数据库中&#xff0c;存储过程是一种预编译的SQL语句集&#xff0c;可以被多次调用。在MySQL中使用存储过程查询到结果后&#xff0c;有时候需要对这些结果进行循环处理。 1. 创建表 CREATE TABLE t_job (job_id int(11) unsigned NOT NULL AUTO_INCREMENT,job_name v…

手写tomcat(Ⅰ)——tomcat原理

Tomcat简介 众所周知&#xff0c;动态web项目基本就是使用了tomcat作为服务端 动态web项目的目录结构 Tomcat是一个轻量级的服务器&#xff0c;其实就是一个Java程序&#xff0c;能够作为一个服务端去接收客户端的请求&#xff0c;并返回给客户端响应 Tomcat本身是一个容器…

基于Keras的手写数字识别(附源码)

目录 引言 为什么要创建虚拟环境&#xff0c;好处在哪里&#xff1f; 源码 我修改的部分 调用本地数据 修改第二层卷积层 引言 本文是博主为了记录一个好的开源代码而写&#xff0c;下面是代码出处&#xff01;强烈建议收藏&#xff01;【深度学习实战—1】&#xff1a…

5、sqlmap注入post类型+os-shell

题目&#xff1a;青少年&#xff1a;Easy_SQLi 1、打开网页&#xff0c;是一个登入表单 2、判断注入类型&#xff0c;是一个字符注入&#xff0c;使用or直接绕过密码进去了 3、上bp抓取数据包&#xff0c;sqlmmap用post注入走一遍&#xff0c;找到数据库&#xff0c;账号密码&…

【Linux】TCP协议【中】{确认应答机制/超时重传机制/连接管理机制}

文章目录 1.确认应答机制2.超时重传机制&#xff1a;超时不一定是真超时了3.连接管理机制 1.确认应答机制 TCP协议中的确认应答机制是确保数据可靠传输的关键部分。以下是该机制的主要步骤和特点的详细解释&#xff1a; 数据分段与发送&#xff1a; 发送方将要发送的数据分成一…

Linux网络编程:HTTP协议

前言&#xff1a; 我们知道OSI模型上层分为应用层、会话层和表示层&#xff0c;我们接下来要讲的是主流的应用层协议HTTP&#xff0c;为什么需要这个协议呢&#xff0c;因为在应用层由于操作系统的不同、开发人员使用的语言类型不同&#xff0c;当我们在传输结构化数据时&…

JVM-调优之-如何使用arthas-观察jvm-cpu-内存-垃圾回收等信息

前言&#xff1a; 可以简单代替把dump文件下载下来后用visualvm分析了&#xff1b;跟visualvm类似的&#xff1b; docker中如何安装arthas看这个&#xff1a;docker中怎么使用arthas_arthas 集成到容器镜像-CSDN博客 curl -O https://arthas.aliyun.com/arthas-boot.jar wget …

闲话 .NET(5):.NET Core 有什么优势?

前言 .NET Core 并不是 .NET FrameWork 的升级版&#xff0c;它是一个为满足新一代的软件设计要求而从头重新开发的开发框架和平台&#xff0c;所以它没有 .NET FrameWork 的历史包袱&#xff0c;相对于 .NET FrameWork&#xff0c;它具备很多优势。 .NET Core 有哪些优势&am…

列主元消去法和矩阵三角分解法求解线性方程组

目录 列主元消去法矩阵三角分解法 列主元消去法 构建增广矩阵&#xff1a; 将线性方程组写成矩阵形式 &#x1d434;&#x1d44b;&#x1d435;&#xff0c;并将系数矩阵 &#x1d434;与常数向量 &#x1d435;组成增广矩阵 [&#x1d434;∣&#x1d435;]。选择主元&#…

ACW石子合并-XMUOJ元素共鸣:唤醒神之眼 -区间DP

题目 思路 话不多说&#xff0c;直接上代码 代码 /* ACW石子合并-XMUOJ元素共鸣&#xff1a;唤醒神之眼 JinlongW-2024/05/25 区间DP 当i<j时&#xff0c;f[i][j]min(f[i][k]f[k][j]s[j]-s[i-1]) 当ij时&#xff0c;f[i][j]0 最终答案&#xff1a;f[1][n] *//* 区间DP…