最小生成树(Prim、Kruskal)
MST
引入
现在有一个连通图,他有\(N\)个节点,\(M\)条边
当我们砍掉一些边时,它会变成一棵树,其剩下的边权之和即为这棵树的权,当剩下的权值最小时,称这棵树为此图的最小生成树,即MST
大致思路
很容易想到,比起砍掉一些边,选择保留一些边更加容易。我们应该在可选择的范围内应该紧着权值小的边保留。
算法其一:Prim
算法基于贪心的思想
因为要生成一棵树,最后每个节点都要被连接,所以我们可以每次选择一个点并将其连接进来,当如此进行直到每个点都被连接进来时,自然就构建完成了。
实现思路
每次选择能被一条边连到树中的,最近的点。但如果直接这样遍历的话时间复杂度就海了去了。
再仔细思考一下这个算法的实现过程,就会发现他很像dijkstra,所以我们可以尝试像dijkstra一样实现。
定义一个\(dis[]\)数组并初始化为INF[^1],表示每个点连接到树中最小的距离。定义一个\(vis[]\)数组表示这个点是否在树中。
循环执行\(N\)次{
- 找到此时不在树中,离树最近的点(可以用堆优化);
- 在\(vis[]\)标记;
- 遍历以K为起点的边并更新\(dis[]\)的值;
}
废话不多说,直接给出堆优化版代码:
#include<bits/stdc++.h>
using namespace std;
struct edge{
int u,v,w,next;
}mp[1000006];//存边
struct node{
int id,dis;
bool operator<(node rhs)const{//千万记得const
return dis>rhs.dis;
}//因为每次取最小的,所以要用小根堆,把小于重载为大于
};
int n,m;
int top,idx[100005];
int dis[100005];
bool vis[100005];
priority_queue<node>q;
void add(int u,int v,int w){//加边
mp[++top].u=u;
mp[top].v=v;
mp[top].w=w;
mp[top].next=idx[u];
idx[u]=top;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,w;cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);//双向边
}
memset(dis,0x3f,sizeof(dis));
q.push((node){1,0});
dis[1]=0;//初始化
while(!q.empty()){
int sp=q.top().id;//找到离树最近的点
q.pop();//记得删掉(我绝对没有经常忘)
if(vis[sp])continue;//因为多次往堆里放,所以可能已经在树里
vis[sp]=1;//如果不在,就连到树里,代价为dis[sp]
while(1)cout<<"The plagiarist is shameful";
//自己翻译
for(int i=idx[sp];i!=0;i=mp[i].next){
//如果不判断是否已经在树中dis的值可能会改变,不影响算法但最后统计代价的时候会麻烦
//注意,dis[]表示的是到树的距离,此时点sp已经在树中,所以直接和边长比较而不要加上dis[sp]
if(!vis[mp[i].v]&&dis[mp[i].v]>mp[i].w){
dis[mp[i].v]=mp[i].w;
q.push((node){mp[i].v,dis[mp[i].v]});
} //一种写法,可以压行
}
}
int ans=0;
for(int i=1;i<=n;i++)
if(dis[i]>1e9){//如果还有没被更新的说明
cout<<"orz";//,也就是说以一为起点走不到这个点,说明不连通
return 0;
}else
ans+=dis[i];
cout<<ans;//统计总花费
return 0;
}
算法其二:Kruskal
(前置技能:并查集基础)
如果说上面的Prim是从点的角度来考虑,尽量每次都选代价最小的点;那么Kruskal就是从边的角度来考虑,每次都考虑权值最小的边。
基本思路大致是每次都找出还没考虑过的、权值最小的边,如果他的两端之前没有被联通,就加入这条边,然后将两端所在集合连接。
但别忘了,我们要运行\(m\)次,每次找到最小的边,再跑一遍搜索,最坏情况下时间复杂度甚至会飞升到恐怖的\(O[m\cdot (m+n)]\)(两层\(m\)循环加上最坏情况下可能每次都要跑一遍\(O(n)\)搜索),再看一眼模板的数据范围:

很显然,这种复杂度想都不用想,直接寄。别说O2了,就算O\(^2\)来了都救不了。那我们该如何优化呢?
这个时候就轮到并查集出马了,关于基本并查集这里不再赘述,如果还不会就去看看看这里。
如果我们用上带路径压缩的并查集,那么时间复杂度将大幅减少到近似\(O(m^2)\)。
再回想一下整个过程,我们是不是只用到了每条边的两端点和长度,根本就没有搜索或者遍历这个点发出的每一条边?
这样的话我们甚至可以只保留最基本的边集数组,然后将它按照长度排序,这样就又减少了一层循环,总体的时间复杂度就进化到了排序的\(O(mlogm)\)。
直接上代码:
#include <bits/stdc++.h>
using namespace std;
struct edge{
int u,v,w;
}mp[200005];//只使用边集数组存边就行
int n,m,ans,cnt;
int f[5003];
bool cmp(edge lhs,edge rhs){//sort用的比较函数
return lhs.w<rhs.w;
}
int root(int x){//并查集找根函数
if(f[x]==0)return x;
return f[x]=root(f[x]);
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>mp[i].u>>mp[i].v>>mp[i].w;
sort(mp,mp+1+m,cmp);//排序
for(int i=1;i<=m;i++){
if(root(mp[i].u)==root(mp[i].v))
continue;//如果之前这两端已经被联通,那再加入这条边就会形成环,就不再是树了
cnt++;//统计选择了几条边
ans+=mp[i].w;//统计代价
f[root(mp[i].u)]=root(mp[i].v);
if(cnt==n-1)
break;//如果已经用了n-1条边就已经是一棵树了,不需要再往后考虑
}
if(cnt<n-1)//连n-1条边都没有说明不连通
cout<<"orz";
else
cout<<ans;
return 0;
}
最小生成树(Prim、Kruskal)的更多相关文章
- 最小生成树 Prim Kruskal
layout: post title: 最小生成树 Prim Kruskal date: 2017-04-29 tag: 数据结构和算法 --- 目录 TOC {:toc} 最小生成树Minimum ...
- 邻接矩阵c源码(构造邻接矩阵,深度优先遍历,广度优先遍历,最小生成树prim,kruskal算法)
matrix.c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include < ...
- 数据结构学习笔记05图(最小生成树 Prim Kruskal)
最小生成树Minimum Spanning Tree 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边. 树: 无回路 |V|个顶 ...
- 布线问题 最小生成树 prim + kruskal
1 : 第一种 prime 首先确定一个点 作为已经确定的集合 , 然后以这个点为中心 , 向没有被收录的点 , 找最短距离( 到已经确定的点 ) , 找一个已知长度的最小长度的 边 加到 s ...
- POJ 1258 Agri-Net(最小生成树 Prim+Kruskal)
题目链接: 传送门 Agri-Net Time Limit: 1000MS Memory Limit: 10000K Description Farmer John has been elec ...
- 最小生成树-Prim&Kruskal
Prim算法 算法步骤 S:当前已经在联通块中的所有点的集合 1. dist[i] = inf 2. for n 次 t<-S外离S最近的点 利用t更新S外点到S的距离 st[t] = true ...
- 邻接表c源码(构造邻接矩阵,深度优先遍历,广度优先遍历,最小生成树prim,kruskal算法)
graph.c #include <stdio.h> #include <stdlib.h> #include <limits.h> #include " ...
- poj1861 最小生成树 prim & kruskal
// poj1861 最小生成树 prim & kruskal // // 一个水题,为的仅仅是回味一下模板.日后好有个照顾不是 #include <cstdio> #includ ...
- 最小生成树---Prim算法和Kruskal算法
Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (gra ...
- 最小生成树(prim&kruskal)
最近都是图,为了防止几次记不住,先把自己理解的写下来,有问题继续改.先把算法过程记下来: prime算法: 原始的加权连通图——————D被选作起点,选与之相连的权值 ...
随机推荐
- Nep2023的wp
0x00 闲言碎语 2023.8.14 记录11-13的紧张刺激.46名结赛. 非常高兴能够参加NepCTF2023,以一个初出茅庐的新人的身份参加.ctf的乐趣在于学习和探索,同时我也有想证明自己的 ...
- [ABC150F] Xor Shift
2023-03-10 题目 题目传送门 翻译 翻译 难度&重要性(1~10):6 题目来源 AtCoder 题目算法 KMP,Z函数 解题思路 首先是按位确定,令 \(t(i,j)\) 表示 ...
- redis分布式锁,setnx+lua脚本的java实现
1 前言 在现在工作中,为保障服务的高可用,应对单点故障.负载量过大等单机部署带来的问题,生产环境常用多机部署.为解决多机房部署导致的数据不一致问题,我们常会选择用分布式锁. 目前其他比较常见的实现方 ...
- 解密Prompt系列14. LLM Agent之搜索应用设计:WebGPT & WebGLM & WebCPM
前两章,我们分别介绍了基于微调和prompt的工具调用方案,核心都是如何让大模型和工具进行交互,包括生成工具调用语句和处理工具调用请求.不过在实际应用中,想要设计一个可以落地的LLM Agent,需要 ...
- 手把手教你搭建springbootsecurity+jwt,全面了解
研究了两周了springbootsecurity+jwt的使用,终于搭起来了,这里跟大家分享下. 首先,不了解jwt的可以提前去查下相关资料,我之前也有讲过,大家可以先看下: https://www ...
- 给你的模糊测试开开窍——定向灰盒模糊测试(Directed Greybox Fuzzing)综述
本文系原创,转载请说明出处 Please Subscribe Wechat Official Account:信安科研人,获取更多的原创安全资讯 原论文:<The Progress, Cha ...
- 【Python进阶-PyQt5】00搭建PyQt5环境
1.创建独立开发虚拟环境 1.1虚拟环境简介 我们编写的程序,有时用到的Python库是不一样的,比如说开发桌面应用程序我们主要用到PyQt5相关的Python库.开发Web应用程序我们主要用到Dja ...
- 《最新出炉》系列初窥篇-Python+Playwright自动化测试-17-处理鼠标悬停
1.简介 有些测试场景或者事件,playwright根本就没有直接提供方法去操作,而且也不可能把各种测试场景都全面覆盖提供方法去操作.比如:就像鼠标悬停,一般测试场景鼠标悬停分两种常见,一种是鼠标悬停 ...
- Shell 文件或目录操作符(-e、-d、-f、-r、-w、-x)
操作符 操作符 含义-e 判断对象是否存在(Exist),若存在则结果为真-d 判断对象是否为目录(Directory),是则为真-f 判断对象是否为一般文件(File),是则为真-r 判断对象是否有 ...
- Java并发编程和多线程的区别
并发编程: 并发编程是一种编程范式,它关注的是编写能够正确和高效处理多个并发任务的程序.并发编程不仅包括多线程,还包括了处理多个独立任务的各种技术和模式,如进程.协程.分布式编程等.并发编程的目标是实 ...