【数据结构与算法】最小生成树之普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法

news2025/2/22 1:17:59

🌱博客主页:大寄一场.

🌱系列专栏:数据结构与算法

😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注
75486fdc2eee4efba3dfc46f574e64ef.gif#pic_center

目录

前言

一、最小生成树的概念

二、最小生成树的求解方法

三、练习题

四、最小生成树在实际应用中的例子


前言

最近非科班的同学学到了最小生成树并询问我,于是想趁热打火,来总结顺便复习一下~

最小生成树(Minimum Spanning Tree,简称MST)是一个无向连通图中包含所有顶点的最短边集。在许多实际问题中,找到一个最小生成树对于理解和解决这些问题至关重要。本文将介绍最小生成树的概念、求解方法以及其在实际应用中的一些例子。

一、最小生成树的概念

假设我们有一个无向连通图G=(V,E),其中V是顶点集合,E是边集合。我们需要找到一个最小生成树,使得每个顶点都至少与一条边相连。这个最小生成树就是MST。

二、最小生成树的求解方法

1.Prim算法 Prim算法是一种贪心算法,用于在具有有向边的加权图中寻找最小生成树。算法的基本思想是从任意一个顶点开始,沿着权重最小的边进行扩展,直到找到整个MST


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define INF INT_MAX

// 邻接矩阵表示的无向图
typedef struct {
    int V; // 顶点数
    int E; // 边数
    int G[100][100]; // 邻接矩阵
} Graph;

// 获取边的权重
int getWeight(Graph G, int u, int v) {
    return G[u][v];
}

// Kruskal算法求最小生成树
Graph primMST(Graph G) {
    int V = G.V; // 顶点数
    int E = G.E; // 边数
    int parent[V]; // 父节点数组
    int dist[V]; // 从源点到每个顶点的距离数组
    int i, u, v;
    int minCost = 0; // 总代价
    int edgeCount = 0; // 已选边的数量
    Graph mstEdges; // MST边集合
    memset(parent, -1, sizeof(parent)); // 初始化父节点为-1
    memset(dist, INF, sizeof(dist)); // 初始化距离为正无穷大
    priorityQueueNode pq; // 优先队列头结点
    pq.data = (void*)&dist[0];
    pq.index = 0;
    memset(mstEdges.G, 0, sizeof(mstEdges.G)); // 将邻接矩阵清零
    mstEdges.V = V;
    mstEdges.E = 0;
    do { // 不断扩展最小生成树,直到不存在增广路为止
        u = pq.index; // 取出距离源点最近的顶点u
        if (u == -1) break; // 如果已经没有顶点可选了,跳出循环
        pq.index = parent[u]; // 将当前顶点更新为其父节点
        edgeCount++; // 已选边的数量加1
        for (i = 0; i < G.V; i++) { // 遍历所有顶点
            v = i; // 从当前顶点开始选择下一个顶点v
            if (dist[v] > dist[u] + G.G[u][v]) { // 如果从u到v的距离比从u到源点的距离更短,则更新距离和优先级队列头结点
                dist[v] = dist[u] + G.G[u][v];
                pq.data = (void*)&dist[0];
                pq.index = i;
            } else if (i != u && v != u) { // 如果当前顶点u不是目标顶点,且从u到v的距离比从u到源点的距离更短,则将边的权重加入到最小生成树中,并更新优先级队列头结点的位置
                mstEdges.G[edgeCount] = getWeight(G, u, v);
                pq.data = (void*)&mstEdges.G[0];
                pq.index = edgeCount++;
            } else if (i == u && v != u) { // 如果当前顶点u是目标顶点,但从u到v的距离比从u到源点的距离更短,则将边的权重加入到最小生成树中,并将当前顶点更新为其父节点的值为已选边的数量减一(因为此时已经找到了一条增广路径)
                mstEdges.G[edgeCount] = getWeight(G, u, v);
                parent[v] = edgeCount--; // 将当前顶点的父节点设为已选边的数量减一(因为此时已经找到了一条增广路径)
            } else if (i != u && v == u) continue; // 如果当前顶点u是目标顶点且从u到v的距离等于从u到源点的距离,则不需要进行任何操作,直接跳过本次循环继续下一次循环迭代
        }
        minCost += mstEdges.G[mstEdges.E-1]; // 将总代价加上已选边的权重之和作为新的总代价
        mstEdges.E++; // 将已选边的计数加一,表示又选了一条边加入到最小生成树中
    } while (edgeCount < V); // 当已选边的数量小于顶点数时,继续扩展最小生成树直到不存在增广路为止

2.Kruskal算法 Kruskal算法也是一种贪心算法,用于在具有有向边的加权图中寻找最小生成树。算法的基本思想是每次选择权重最小的边来将两个顶点连接起来,直到找到整个MST。


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define INF INT_MAX

// 邻接矩阵表示的无向图
typedef struct {
    int V; // 顶点数
    int E; // 边数
    int G[100][100]; // 邻接矩阵
} Graph;

// 获取边的权重
int getWeight(Graph G, int u, int v) {
    return G[u][v];
}

// Kruskal算法求最小生成树
Graph primMST(Graph G) {
    int V = G.V; // 顶点数
    int E = G.E; // 边数
    int parent[V]; // 父节点数组
    int dist[V]; // 从源点到每个顶点的距离数组
    int i, u, v;
    int minCost = 0; // 总代价
    int edgeCount = 0; // 已选边的数量
    Graph mstEdges; // MST边集合
    memset(parent, -1, sizeof(parent)); // 初始化父节点为-1
    memset(dist, INF, sizeof(dist)); // 初始化距离为正无穷大
    priorityQueueNode pq; // 优先队列头结点
    pq.data = (void*)&dist[0];
    pq.index = 0;
    memset(mstEdges.G, 0, sizeof(mstEdges.G)); // 将邻接矩阵清零
    mstEdges.V = V;
    mstEdges.E = 0;
    do { // 不断扩展最小生成树,直到不存在增广路为止
        u = pq.index; // 取出距离源点最近的顶点u
        if (u == -1) break; // 如果已经没有顶点可选了,跳出循环
        pq.index = parent[u]; // 将当前顶点更新为其父节点
        edgeCount++; // 已选边的数量加1
        for (i = 0; i < G.V; i++) { // 遍历所有顶点
            v = i; // 从当前顶点开始选择下一个顶点v
            if (dist[v] > dist[u] + G.G[u][v]) { // 如果从u到v的距离比从u到源点的距离更短,则更新距离和优先级队列头结点
                dist[v] = dist[u] + G.G[u][v];
                pq.data = (void*)&dist[0];
                pq.index = i;
            } else if (i != u && v != u) { // 如果当前顶点u不是目标顶点,且从u到v的距离比从u到源点的距离更短,则将边的权重加入到最小生成树中,并更新优先级队列头结点的位置
                mstEdges.G[edgeCount] = getWeight(G, u, v);
                pq.data = (void*)&mstEdges.G[0];
                pq.index = edgeCount++;
            } else if (i == u && v != u) { // 如果当前顶点u是目标顶点,但从u到v的距离比从u到源点的距离更短,则将边的权重加入到最小生成树中,并将当前顶点更新为其父节点的值为已选边的数量减一(因为此时已经找到了一条增广路径)
                mstEdges.G[edgeCount] = getWeight(G, u, v);
                parent[v] = edgeCount--; // 将当前顶点的父节点设为已选边的数量减一(因为此时已经找到了一条增广路径)
            } else if (i != u && v == u) continue; // 如果当前顶点u是目标顶点且从u到v的距离等于从u到源点的距离,则不需要进行任何操作,直接跳过本次循环继续下一次循环迭代
        }
        minCost += mstEdges.G[mstEdges.E-1]; // 将总代价加上已选边的权重之和作为新的总代价
        mstEdges.E++; // 将已选边的计数加一,表示又选了一条边加入到最小生成树中
    } while (edgeCount < V); // 当已选边的数量小于顶点数时,继续扩展最小生成树直到不存在增广路为止

三、练习题

对如图所示的带权连通图按照克鲁斯卡尔和普里姆算法得到其最小生成树,请写出生成过程中依次得到的各条边,并计算该最小生成树的权值。

 普里姆算法从任意一个顶点开始,沿着权重最小的边进行扩展

克鲁斯卡尔 每次选择权重最小的边来将两个顶点连接起来

四、最小生成树在实际应用中的例子

最小生成树在很多实际应用中有很广泛的应用,例如路由算法、社交网络分析、电路设计等。下面分别介绍这些领域中的应用案例。

  1. 路由算法
    最小生成树在路由算法中有很重要的应用。例如,在单源最短路径问题中,我们可以使用Prim算法或Kruskal算法来找到最小生成树。同时,最小生成树也可以用于计算网络中每个节点的最短路径。这对于网络优化和资源分配非常重要。

  2. 社交网络分析
    最小生成树在社交网络分析中也有很广泛的应用。例如,我们可以使用最小生成树来确定社交网络中的社区结构。通过将每个节点与它的邻居节点连接起来,并删除具有较小的连通性(即具有较少的邻居节点)的边,我们可以得到一个最小生成树。然后,我们可以通过检查哪些节点之间的边被保留来确定这些节点属于同一个社区。

  3. 电路设计
    最小生成树在电路设计中有很重要的应用。例如,在电路布线中,我们可以使用最小生成树来最小化电路的总长度和电阻。通过将电路中的节点与它们的相邻节点连接起来,并删除具有较小的阻抗(即具有较少的电阻或电容)的边,我们可以得到一个最小生成树。然后,我们可以选择将电阻和电容分配给这个最小生成树上的节点,以最小化总长度和阻抗。

总之,最小生成树在许多领域中都有着广泛的应用。它不仅可以帮助我们解决各种计算问题,还可以帮助我们理解和分析现实世界中的复杂系统。


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

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

相关文章

返回类对象时,什么时候调用拷贝构造函数,什么时候会进行返回值优化(RVO)

#include<iostream> using namespace std;class Person { public:Person(){}Person(int age){m_Age age;}Person(const Person& p){cout << "拷贝构造函数" << endl;}Person fun(){cout << "fun this" << " "…

一步步教你如何剪辑出专业水平的视频

1. 视频字幕制作。媒体梦工厂软件提供了强大的字幕制作功能&#xff0c;可以自主设计字幕的颜色、大小、字体等属性&#xff0c;使字幕更加具有视觉冲击力。"媒体梦工厂软件是一款广受欢迎的影视后期制作软件&#xff0c;自从软件发布以来在行业内有着广泛的应用。本文将会…

使用redis模拟手机验证码发送及消费者与生产者案例

规定一个手机号一天只能请求三次验证码&#xff0c;且每次请求的验证码只有一分钟就会过期 package com.doit.demo;import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool;import java.util.Random; import java.util.Scanner;public class PhoneNum {publ…

Spark基础入门篇 | MapReduce原理 + Spark原理 + PySpark环境搭建 + 简单实战

&#x1f604; 之前简单了解过Spark&#xff0c;并简单用别人的代码跑过pyspark的数据处理和模型的分布式推理&#xff0c;但没做系统的总结&#xff0c;那这篇博客就对Spark做个基础入门讲解&#xff0c;看完基本就算基础入门了&#xff0c;后面再实操就会轻松一些。 文章目录…

Windows本地提权 · 下篇

Windows本地提权&#xff0c;这种提权适用于有一本地个用户的基础上&#xff0c;有一定的权限&#xff0c;无法从webshell上进行提权 目录 BypassUAC提权 原理 关于UAC MFS绕过提权 UAC等级为低绕过测试 UAC等级为中绕过测试 UAC等级为高绕过测试 注意&#xff1a;bypa…

【综述】视频无监督域自适应(VUDA)的小综述

【综述】视频无监督域自适应&#xff08;VUDA&#xff09;的小综述 一篇小综述&#xff0c;大家看个乐子就好&#xff0c;参考文献来自于一篇综述性论文 链接&#xff1a;https://arxiv.org/abs/2211.10412 这次基于三篇有代表性的文章来讲解 X. Song, S. Zhao, J. Yang, H.…

第十篇、基于Arduino uno,用LCD1602(不带IIC的)显示屏显示字符——结果导向

0、结果 说明&#xff1a;可以在LCD1602屏幕上面显示字符&#xff0c;实时的变量&#xff0c;如果是你想要的&#xff0c;可以接着往下看。 1、外观 说明&#xff1a;注意是不带IIC通讯的LCD屏幕&#xff0c;外形如下。 2、连线 说明&#xff1a;需要连接十几根线。 uno——…

#机器学习--深度学习中的优化

#机器学习--深度学习中的优化 引言1、神经网络优化中的挑战1.1、病态1.2、局部极小值1.3、高原、鞍点和其它平坦区域1.4、悬崖1.5、长期依赖、梯度消失与梯度爆炸1.6、非精确梯度1.7、局部和全局结构间的弱对应1.8、优化的理论限制 2、优化算法2.1、随机梯度下降&#xff08;SG…

macos wireshark 抓取https包

1、启动浏览器 1.1 创建空文件 $ touch /Users/zhujl/Downloads/https/mysslkey.log 2、设置wireshark tls属性&#xff0c;指定tls密钥存储文件 2.1 进入Wireshark Preferfences > Protocols > TLS 属性配置 2.2 勾选上Reassemable TLS records spanning multiple …

【网络编程】https协议——加密与窃密的攻防战

目录 一、https协议的介绍 二、加密和解密 1、加密和解密的过程 2、为什么需要加密和解密 3、常见的加密方式 3.1对称加密 3.2非对称加密 3.3数据摘要&#xff08;数据指纹&#xff09; 3.4数字签名 三、https加密解密的方式选择和中间人攻击的方式 1、只使用对称加…

JUC 高并发编程基础篇

JUC 高并发编程基础篇 • 1、什么是 JUC • 2、Lock 接口 • 3、线程间通信 • 4、集合的线程安全 • 5、多线程锁 • 6、Callable 接口 • 7、JUC 三大辅助类: CountDownLatch CyclicBarrier Semaphore • 8、读写锁: ReentrantReadWriteLock • 9、阻塞队列 • 10、ThreadPo…

Android12之MediaMetricsService服务(一百五十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

谷歌云 | 你需要知道的关于软件开发中的人工智能

【本文由 Cloud Ace 整理&#xff0c;Cloud Ace 是谷歌云全球战略合作伙伴&#xff0c;拥有 300 多名工程师&#xff0c;也是谷歌最高级别合作伙伴&#xff0c;多次获得 Google Cloud 合作伙伴奖。作为谷歌托管服务商&#xff0c;我们提供谷歌云、谷歌地图、谷歌办公套件、谷歌…

底层课程导学

目录 一、底层导学 1.课程回顾 2.嵌入式系统分层 3.Linux层次结构 二、ARM课该怎么学 1.课程内容 2.学习方法 三、计算机基础知识 1.计算机的进制 2.总线 四、ARM存储模型 1.三级存储结 五、CPU工作原理 1.CPU工作原理 2.指令的执行过程 3.地址空间 六、ARM体系结构 1.ARM处理器…

自动化测试实战项目(二)连连看外挂

自动化测试和做外挂的原理很相似&#xff0c;都是模拟用户的鼠标和键盘操作, 给自己的程序写自动化就是做测试&#xff0c;给别人的程序写自动化就是外挂了。 本文使用的技术也同样适用制作“对对碰”&#xff0c;"找茬" 之类游戏的外挂。 阅读目录 QQ连连看外挂实…

《面试1v1》垃圾回收机制

我是 javapub&#xff0c;一名 Markdown 程序员从&#x1f468;‍&#x1f4bb;&#xff0c;八股文种子选手。 面试官&#xff1a; 小伙子,跟我聊聊垃圾回收机制吧。什么是垃圾?怎么回收? 候选人&#xff1a; 好的面试官,来吧!垃圾就是那些不再被程序使用的对象。Java 通过…

Spring Boot定时任务

目录 1.概述 2.Spring Boot定时任务 2.1.快速使用 2.2.cron表达式 3.业务示例 3.1.业务描述 3.2.业务实现 4.实现原理 5.自定义线程池 1.概述 在某些业务场景中&#xff0c;需要定时执行一些任务&#xff0c;有可能是定时统计然后生成报表&#xff0c;有可能是定时发…

Python自定义函数

目录 1. 语法 2. 常见用法 2.1. 函数的返回值 2.2. 函数互相调用 3. 实战练习 3.1. 定义执行Linux命令的函数 1. 语法 #定义函数 def 函数名(参数1, 参数2):函数体(代码块)......#调用函数(定义函数时使用了参数&#xff0c;调用也必须使用参数) 函数名(参数1, 参数2) 定…

React学习6 路由

SPA的理解 单页Web应用&#xff08;single page web application&#xff0c;SPA&#xff09;。整个应用只有一个完整的页面。点击页面中的链接不会刷新页面&#xff0c;只会做页面的局部更新。数据都需要通过ajax请求获取, 并在前端异步展现。 什么是路由? 一个路由就是一个…

网络连接管理除了TCP三次握手,还有TCP四次挥手

网络连接管理除了TCP三次握手&#xff0c;还有TCP四次挥手 TCP三次握手&#xff0c;TCP四次挥手 网络连接管理除了TCP三次握手&#xff0c;还有TCP四次挥手TCP三次握手TCP四次挥手总结 TCP三次握手 网络通信建立连接&#xff0c;TCP会进行三次握手&#xff0c;三次握手主要是两…