news 2026/4/16 18:16:01

代码随想录算法训练营Day49 | Prim算法、Kruskal算法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
代码随想录算法训练营Day49 | Prim算法、Kruskal算法

Prim算法

53. 寻宝(第七期模拟笔试)

1.思路

本题是最小生成树的模板题,图中有n个节点,那么一定可以用 n-1 条边将所有节点连接到一起,并且总权重最小。

Prim 算法:从一个顶点开始,逐步“生长”出一棵树。 每次都选择一条权重最小的边,这条边连接一个新顶点到当前已经构建好的树上。这个过程不断重复,直到所有顶点都被包含进来。

Prim 算法核心:

  1. 第一步,选距离生成树最近节点
  2. 第二步,最近节点加入生成树
  3. 第三步,更新非生成树节点到生成树的距离(即更新minDist数组)

这道题的解题思路为:

  1. 初始化

    • 任意选择一个顶点作为树的起点(默认是顶点1)。

    • 创建一个集合(isIntree数组),用来记录哪些顶点已经加入了树。

    • 创建一个距离表(minDist数组),用来记录尚未加入树的顶点,到当前树的最短距离是多少。初始时,所有距离都是“无穷大”。

  2. 循环n-1次(因为最终树有n-1条边)

    • 选距离生成树最近节点

      • 在所有还没加入树的顶点中,找到一个距离当前树最近的顶点。即在minDist表中记录的值最小的顶点。

    • 最近节点加入生成树

      • 将这个找到的顶点标记为“已加入树”。

      • 此时,连接它和树的那条边,就正式成为了最小生成树的一条边。

    • 更新非生成树节点到生成树的距离(即更新minDist数组)

      • 由于树中多了一个新顶点,其他未加入树的顶点到树的距离可能会变得更短

      • 遍历这个新顶点的所有邻居,如果通过新顶点到某个邻居的距离,比该邻居之前记录的距离更短,就更新这个邻居的距离。

  3. 结束

    • 重复n-1次后,所有顶点都已加入树,最小生成树就构建完成了。

    • 我们把每次加入树时所用的那条边的权重(即minDist中最终记录的值)加起来,就是最终结果。

#include <iostream> #include <vector> using namespace std; int main(){ int n,m;cin>>n>>m; vector<vector<int>>graph(n+1,vector<int>(n+1,10001)); for(int i=0;i<m;i++){ int s,t,val;cin>>s>>t>>val; // 因为是双向图,所以两个方向都要填上 graph[s][t]=val; graph[t][s]=val; } // 这个节点是否在树里 vector<bool>isIntree(n+1,false); // 所有节点到最小生成树的最小距离 vector<int>minDist(n+1,10001); for(int i=1;i<n;i++){ // 1、prim三部曲,第一步:选距离生成树最近节点 int minval=10001; int cur=1; for(int j=1;j<=n;j++){ // 选取最小生成树节点的条件: // (1)不在最小生成树里 // (2)距离最小生成树最近的节点 if(!isIntree[j] && minDist[j]<minval){ minval=minDist[j]; cur=j; } } // 2、prim三部曲,第二步:最近节点(cur)加入生成树 isIntree[cur]=true; // 3、prim三部曲,第三步:更新非生成树节点到生成树的距离(即更新minDist数组) for(int j=1;j<=n;j++){ // 更新的条件: // (1)节点是 非生成树里的节点 // (2)与cur相连的某节点的权值 比 该某节点距离最小生成树的距离小 if(!isIntree[j] && graph[cur][j]<minDist[j]){ minDist[j]=graph[cur][j]; } } } int res=0; // 不计第一个顶点,因为统计的是边的权值,v个节点有 v-1条边 for(int i=2;i<=n;i++){ res+=minDist[i]; } cout<<res<<endl; return 0; }

2.拓展

在上述问题的基础上还要打印出最小生成树的每条边。

要存边,我们可以使用一维数组来记录,parent [节点编号] = 节点编号,这样就把一条边记录下来了。minDist 数组记录了最小生成树的边,所以在更新 minDist 数组的时候,就更新 parent 数组来记录对应的边。

#include <iostream> #include <vector> using namespace std; int main(){ int n,m;cin>>n>>m; vector<vector<int>>graph(n+1,vector<int>(n+1,10001)); for(int i=0;i<m;i++){ int s,t,val;cin>>s>>t>>val; graph[s][t]=val; graph[t][s]=val; } vector<bool>isIntree(n+1,false); vector<int>minDist(n+1,10001); vector<int>father(n+1,-1); for(int i=1;i<n;i++){ int minval=10001; int cur=1; for(int j=1;j<=n;j++){ if(!isIntree[j] && minDist[j]<minval){ minval=minDist[j]; cur=j; } } isIntree[cur]=true; for(int j=1;j<=n;j++){ if(!isIntree[j] && graph[cur][j]<minDist[j]){ minDist[j]=graph[cur][j]; father[j]=cur; // 记录边 } } } int res=0; for(int i=2;i<=n;i++){ res+=minDist[i]; } cout<<res<<endl; // 输出 最小生成树边的链接情况 for(int i=1;i<=n;i++){ cout<<i<<"->"<<father[i]<<endl; } return 0; }

3.思考

Prim 算法三部曲:

  1. 第一步,选距离生成树最近节点
  2. 第二步,最近节点加入生成树
  3. 第三步,更新非生成树节点到生成树的距离(即更新minDist数组)

minDist 数组是 prim 算法的灵魂,它帮助 prim 算法完成最重要的一步,就是如何找到距离最小生成树最近的点。

4.Reference:prim算法精讲 | 代码随想录


Kruskal算法

53. 寻宝(第七期模拟笔试)

1.思路

与 Prim 算法从一个顶点“生长”不同,Kruskal 算法的核心思想是直接对所有的进行操作。它按照边的权重从小到大的顺序,依次将边加入图中,只要这条边不会与已经选择的边形成环路,就将其纳入最小生成树。为了高效地判断环路,引入之前的并查集。

Kruscal 的思路:

  • 边的权值升序排序,因为要优先选最小的边加入到生成树里
  • 遍历排序后的边
    • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环
    • 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合
#include <iostream> #include <vector> #include <algorithm> using namespace std; int n,m; struct Edge{ int s,t,val; }; bool cmp(const Edge& a,const Edge& b){ return a.val<b.val; } vector<int>father(n+1,1); void init(){ for(int i=1;i<=n;i++){ father[i]=i; } } int find(int u){ if(u==father[u]){ return u; } return father[u]=find(father[u]); } bool issame(int u,int v){ u=find(u); v=find(v); return u==v; } void join(int u,int v){ u=find(u); v=find(v); if(u==v) return; father[u]=v; } int main(){ cin>>n>>m; vector<Edge> edges; for(int i=0;i<m;i++){ int s,t,val;cin>>s>>t>>val; edges.push_back({s,t,val}); } // 按边的权值对边进行从小到大排序 sort(edges.begin(),edges.end(),cmp); init(); int res=0; // 从头开始遍历边 for(int i=0;i<m;i++){ // 检查这条边的两个顶点是否已经连通 if(!issame(edges[i].s,edges[i].t)){ join(edges[i].s,edges[i].t); res+=edges[i].val; // 这条边可以作为生成树的边,将这条边的权重累加到结果中 } } cout<<res<<endl; return 0; }

2.拓展

在上述问题基础上将最小生成树的边输出。

因为 Kruskal 本来就是直接操作边,边的结构自然清晰。当判断两个节点不在同一个集合的时候,这两个节点的边就加入到最小生成树,此时把生成树的边保存下来就可以了。

#include <iostream> #include <vector> #include <algorithm> using namespace std; int n,m; struct Edge{ int s,t,val; }; bool cmp(const Edge& a,const Edge& b){ return a.val<b.val; } vector<int>father(n+1,1); void init(){ for(int i=1;i<=n;i++){ father[i]=i; } } int find(int u){ if(u==father[u]){ return u; } return father[u]=find(father[u]); } bool issame(int u,int v){ u=find(u); v=find(v); return u==v; } void join(int u,int v){ u=find(u); v=find(v); if(u==v) return; father[u]=v; } int main(){ cin>>n>>m; vector<Edge> edges; for(int i=0;i<m;i++){ int s,t,val;cin>>s>>t>>val; edges.push_back({s,t,val}); } sort(edges.begin(),edges.end(),cmp); init(); int res=0; // 存储最小生成树的边 vector<Edge> result; for(int i=0;i<m;i++){ if(!issame(edges[i].s,edges[i].t)){ join(edges[i].s,edges[i].t); // 保存最小生成树的边 result.push_back(edges[i]); res+=edges[i].val; } } cout<<res<<endl; for(Edge edge:result){ cout<<edge.s<<"->"<<edge.t<<" : "<<edge.val<<endl; } return 0; }

3.思考

Kruskal 与 prim 的关键区别在于,prim维护的是节点的集合,而 Kruskal 维护的是边的集合。 如果一个图中,节点多,但边相对较少,那么使用Kruskal 更优。

所以在稀疏图中,用 Kruskal 更优。 在稠密图中,用 prim 算法更优。

Prim 算法时间复杂度为 O(n^2),其中 n 为节点数量,它的运行效率和图中边树无关,适用稠密图。

Kruskal算法时间复杂度为 O(nlogn),其中 n 为边的数量,适用稀疏图。

4.Reference:kruskal算法精讲 | 代码随想录

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 12:25:34

34、Linux系统安全防护全攻略

Linux系统安全防护全攻略 1. 文件加密 若仅需对文件进行加密,且无需他人解密,可使用GPG进行对称加密。操作步骤如下: 1. 执行命令 gpg -o secret.gpg -c somefile ,GPG会提示输入密码并要求再次输入以确认。之后,GPG会使用从密码生成的密钥对文件进行加密。 2. 若要…

作者头像 李华
网站建设 2026/4/15 10:05:04

36、网络安全测试工具与互联网服务解析

网络安全测试工具与互联网服务解析 在网络安全和互联网服务的领域中,有许多强大的工具和概念值得我们去探索。下面将详细介绍一些常见的安全测试工具以及互联网服务的相关知识。 安全测试工具 在进行网络安全测试时,有很多自动化工具可供选择。这些工具的功能各有不同,有…

作者头像 李华
网站建设 2026/4/16 12:25:36

陪读蛙Read Frog配置API Key图文教程

一、安装陪读蛙Read Frog 请前往官方地址下载并安装陪读蛙Read Frog&#xff1a; https://www.readfrog.app/zh 在应用商店安装。如下图所示&#xff1a; 安装后将会自动跳转&#xff0c;选择合适的母语。如下图所示&#xff1a; 在浏览器插件中&#xff0c;将陪读蛙Read Frog…

作者头像 李华
网站建设 2026/4/16 10:14:13

45、Linux技术全面指南:符号、网络、应用与安全解析

Linux技术全面指南:符号、网络、应用与安全解析 1. 符号与数字表示 在Linux系统里,有不少特殊的符号和数字表示方法,它们在不同场景下发挥着关键作用。例如,“.”代表当前目录,“..”表示父目录,“/”是根目录,同时在文件系统组织中也有重要意义。“[ ]”作为通配符占…

作者头像 李华
网站建设 2026/4/16 10:26:30

数据为核,驱动智造:产品数据管理(PDM)的核心价值与选型指南

在智能制造与数字化转型的浪潮中&#xff0c;产品研发数据已成为企业的核心战略资产。如何有效管理海量、复杂且关联紧密的产品数据&#xff0c;确保其准确性、一致性与可追溯性&#xff0c;是制造企业提升效率、缩短上市时间的关键。产品数据管理&#xff08;Product Data Man…

作者头像 李华
网站建设 2026/4/16 10:27:54

Linux线程:轻量高效但需谨慎

Linux线程概述Linux线程是轻量级进程&#xff08;LWP&#xff09;&#xff0c;属于某个进程并共享其资源&#xff08;如内存&#xff09;&#xff0c;但各自拥有独立的栈区。相比进程&#xff0c;线程的优势在于创建开销小&#xff08;仅需分配8MB栈区&#xff0c;而进程需3GB空…

作者头像 李华