图论算法(五)最小生成树Prim算法
最小生成树\(Prim\)算法
我们通常求最小生成树有两种常见的算法——\(Prim\)和\(Kruskal\)算法,今天先总结最小生成树概念和比较简单的\(Prim\)算法
Part 1:最小生成树基础理论
定义
一个有 \(n\) 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 \(n\) 个结点,并且有保持图连通的最少的边。
——来自百度百科
我们用比较通俗的语言来讲:(百度百科的解释实在是太鬼了,我这个明白人都看着迷糊)
给定一张包含\(n\)个点\(m\)条边的连通带权无向图\(G\),我们从中选出\(n-1\)条边,使得这\(n\)个点互相连通
连通之后,发现所选的\(n-1\)条边和图中的\(n\)个点,构成了一棵树,我们称之为:“生成树”
我们对这棵生成树的所有边权求和,得到一个数\(size\),称之为:“生成树大小”
现在我们就可以字面上理解“最小生成树”是什么意思了——一个图的所有生成树中\(size\)值最小的那一个,就是这个图的最小生成树(\(Minimum\) \(Spanning\) \(Tree\),简称\(MST\))
其实你可以更简单的理解为:最小生成树就是连通\(n\)个点所花费的最小代价
现在我将展示我的画图技巧,举一个例子:
现在给定这张无向图\(G\),我们的任务是求出它的\(MST\)
使用肉眼观察法,我们得到的\(MST\)应该是选择\((1,3),(4,3),(2,4)\)这三条边,权值和为\(5\),也就是我们只花了\(5\)的代价,就把所有点连起来了
通过枚举所有生成树可以发现:不论怎么选,上述方案的权值和一定是最小的,所以它的最小生成树大小为\(5\),包含\((1,3),(4,3),(2,4)\)这三条边
定理
定理:任意一棵最小生成树一定包含无向图中权值最小的边
定理证明:
反证法,假设无向图\(G\)的最小生成树不包含这个权值最小的边(把这个最小权值边设为\(e\)且\(e\)连通点\(x,y\),权值为\(z\))
把\(e\)添加到不包含\(e\)的这棵最小生成树里去,一定会形成一个环,并且环上的每一条\(e\)以外的边的权值一定比\(z\)大
此时,我们随便去掉一条\(e\)以外的边,整个图仍然连通成一棵树,且权值和\(size\)更小(因为加入\(z\),去掉一个大于\(z\)的权)
发现这与一开始的假设矛盾,所以假设不成立,原命题成立,证毕。
Part 2:\(Prim\)算法
\(Prim\)算法原理
这里我们抛开正确性证明,只谈原理(正确性证明是计算机科学家的事,我们需要的是了解与应用)
最初,\(Prim\)算法仅确定\(1\)号节点已经在最小生成树中
\(1、\)设已经选入最小生成树的节点集合为\(T\),没有选入的节点集合为\(S\)
\(2、\)找到一条边\(e(x,y,z)\)(连接\(x,y\),权值为\(z\)),使得\(x\in S,y\in T\)且\(z\)最小
\(3、\)在集合\(S\)中删除\(y\),加入到集合\(T\)中,并累加\(z\)到\(size\)中
\(4、\)重复上述操作,直到集合\(S\)为空为止,此时\(size\)就是最小生成树的大小
具体到代码里可以这么写:
维护一个数组\(dis\),若节点\(i\in S\)则\(dis[i]\)表示节点\(i\)与集合\(T\)中的节点之间权值最小的边的权值,若\(i\in T\)则\(dis[i]\)表示\(i\)被加入\(T\)时选出的最小边的权值
发现这好像与\(dijkstra\)算法要维护的东西有点像:
\(dijkstra\)算法维护一个未知最短路的点到已知最短路的点的最短距离,每次确定到达一个点的最短路,用于更新其他未知点到已知点的最短距离
而\(Prim\)算法维护一个未加入最小生成树的点到已加入最小生成树的点的边权最小值,每次选择一个点加入到最小生成树,更新边权最小值
所以我们可以用一个数组\(vis\)来标记一个节点是否属于集合\(S\),每次从未标记的节点中选出\(dis\)值最小的,把它标记,加入集合\(T\),扫描这个点的所有出边,更新另一个端点的\(dis\)值
最后,当集合\(S\)为空时,算法结束,最小生成树大小为\(\sum_{x=2}^{n}dis[x]\),你也可以直接在选出边权最小值的时候直接累加,不再求和
另外,\(Prim\)算法的时间复杂度为\(O(n^2)\),算不上太优秀,但是因为有求最小值的操作,所以我们可以把\(dis\)数组换成一个小根堆,把时间复杂度优化到\(O(mlogn)\)
\(Code\) \(O(n^2)\)
#include<cstring>
#include<cstdio>
#define N 5010
using namespace std;
int f[N][N],dis[N],vis[N],m,n,total;
void prim(int x){
memset(dis,0x7f,sizeof(dis));//赋初值无穷大
memset(vis,1,sizeof(vis));//把所有点标记为在集合S中
dis[x]=0;//1号点dis为0
for(int i=1;i<=n;i++){
int k=0;
for(int j=1;j<=n;j++)
if(vis[j]!=0&&(dis[j]<dis[k])) k=j;//集合S中最小的dis值的点为k
vis[k]=0;//把k加入到最小生成树
total+=dis[k];//把选择边的边权累加到total里
for(int j=1;j<=n;j++)//用节点k更新dis中其他的值
if(vis[j]!=0&&(f[k][j]<dis[j])) dis[j]=f[k][j];//更新一个不在最小生成树中的点j的dis值
}
}
int main(){
scanf("%d%d",&n,&m);
memset(f,0x7f,sizeof(f));
for(int i=1,x,y,z;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);//初始化邻接矩阵
if(f[x][y]<z) continue;
f[x][y]=z;
f[y][x]=z;
}
for(int i=1;i<=n;i++)
f[i][i]=0;
prim(1);//执行Prim算法
printf("%d\n",total);
return 0;
}
2020/8/15 13:37 update:增加了堆优化\(Prim\)的代码
\(Code\) \(O(mlogn)\)
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<algorithm>
#include<set>
#include<map>
#include<utility>
#include<iostream>
#include<list>
#include<ctime>
#include<cmath>
#include<cstdlib>
#include<iomanip>
typedef long long int ll;
inline int read(){
int fh=1,x=0;
char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-') fh=-1;ch=getchar(); }
while('0'<=ch&&ch<='9'){ x=(x<<3)+(x<<1)+ch-'0';ch=getchar(); }
return fh*x;
}
inline int _abs(const int x){ return x>=0?x:-x; }
inline int _max(const int x,const int y){ return x>=y?x:y; }
inline int _min(const int x,const int y){ return x<=y?x:y; }
inline int _gcd(const int x,const int y){ return y?_gcd(y,x%y):x; }
inline int _lcm(const int x,const int y){ return x*y/_gcd(x,y); }
const int maxn=100005;
struct Edge{
int cost,to;
};
//以上均为缺省源,下面是主要部分
int n,m;//n个点m条边的无向图G
std::vector<Edge>v[maxn];//vector邻接表建图
//Prim算法
std::priority_queue< std::pair<int,int> >Q; //小根堆优化,第一维存权值,第二维存点标号
int vis[maxn];
inline int Prim(){
int MST=0;
vis[1]=1;
for(unsigned int i=0;i<v[1].size();i++)//把1号点的所有连边全部入堆
Q.push(std::make_pair(-v[1][i].cost,v[1][i].to));//入堆时取反变成了小根堆
while(Q.size()!=0){
while(vis[Q.top().second]){
Q.pop();
if(Q.size()==0) return MST;//如果这里堆中没有元素,下一次循环时会访问无效内存,导致RE
//所以特判一下,如果堆中没有元素,那么说明已经完成了最小生成树的求解,返回MST即可
}
int x=Q.top().second;
MST-=Q.top().first;//注意存边的时候取反了,这里再取反
Q.pop();
vis[x]=true;//把点x加入最小生成树
for(unsigned int i=0;i<v[x].size();i++){
int y=v[x][i].to,z=v[x][i].cost;
if(!vis[y]) Q.push(std::make_pair(-z,y)); //如果y不在生成树里,才会入堆
}
}
return MST;
}
int main(){
n=read(),m=read();
for(int i=0,x,y,z;i<m;i++){
x=read(),y=read(),z=read();
v[x].push_back((Edge){z,y});
v[y].push_back((Edge){z,x});//建图
}
int ans=Prim();
printf("%d\n",ans);
return 0;
}
关于最小生成树\(Prim\)算法的分享就到这里,感谢您的阅读!
图论算法(五)最小生成树Prim算法的更多相关文章
- 最小生成树,Prim算法与Kruskal算法,408方向,思路与实现分析
最小生成树,Prim算法与Kruskal算法,408方向,思路与实现分析 最小生成树,老生常谈了,生活中也总会有各种各样的问题,在这里,我来带你一起分析一下这个算法的思路与实现的方式吧~~ 在考研中呢 ...
- 数据结构代码整理(线性表,栈,队列,串,二叉树,图的建立和遍历stl,最小生成树prim算法)。。持续更新中。。。
//归并排序递归方法实现 #include <iostream> #include <cstdio> using namespace std; #define maxn 100 ...
- 最小生成树Prim算法(邻接矩阵和邻接表)
最小生成树,普利姆算法. 简述算法: 先初始化一棵只有一个顶点的树,以这一顶点开始,找到它的最小权值,将这条边上的令一个顶点添加到树中 再从这棵树中的所有顶点中找到一个最小权值(而且权值的另一顶点不属 ...
- 最小生成树--Prim算法,基于优先队列的Prim算法,Kruskal算法,Boruvka算法,“等价类”UnionFind
最小支撑树树--Prim算法,基于优先队列的Prim算法,Kruskal算法,Boruvka算法,“等价类”UnionFind 最小支撑树树 前几节中介绍的算法都是针对无权图的,本节将介绍带权图的最小 ...
- 最小生成树—prim算法
最小生成树prim算法实现 所谓生成树,就是n个点之间连成n-1条边的图形.而最小生成树,就是权值(两点间直线的值)之和的最小值. 首先,要用二维数组记录点和权值.如上图所示无向图: int map[ ...
- HDU1102 最小生成树prim算法
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1102 题意:给出任意两个城市之间建一条路的时间,给出哪些城市之间已经建好,问最少还要多少时间使所有的城 ...
- Highways POJ-1751 最小生成树 Prim算法
Highways POJ-1751 最小生成树 Prim算法 题意 有一个N个城市M条路的无向图,给你N个城市的坐标,然后现在该无向图已经有M条边了,问你还需要添加总长为多少的边能使得该无向图连通.输 ...
- SWUST OJ 1075 求最小生成树(Prim算法)
求最小生成树(Prim算法) 我对提示代码做了简要分析,提示代码大致写了以下几个内容 给了几个基础的工具,邻接表记录图的一个的结构体,记录Prim算法中最近的边的结构体,记录目标边的结构体(始末点,值 ...
- 算法起步之Prim算法
原文:算法起步之Prim算法 prim算法是另一种最小生成树算法.他的安全边选择策略跟kruskal略微不同,这点我们可以通过一张图先来了解一下. prim算法的安全边是从与当前生成树相连接的边中选择 ...
随机推荐
- JavaScript经典实例(浏览器事件)
跨浏览器事件 1.跨浏览器添加事件 function addEvent(obj,type,fn){ if(obj.addEventListener){ obj.addEventListener(typ ...
- Python语言及其应用|PDF高清完整版免费下载|百度云盘|Python
百度云盘:Python语言及其应用PDF高清完整版免费下载 提取码:6or6 内容简介 本书介绍Python 语言的基础知识及其在各个领域的具体应用,基于最新版本3.x.书中首先介绍了Python 语 ...
- Linux系统查看硬件信息神器,比设备管理器好用100倍!
大家都知道,当我们的 Linux 系统计算机出现问题时,需要对其排除故障,首先需要做的是找出计算机的硬件信息.下面介绍一个简单易用的应用程序--HardInfo,你可以利用它来显示你电脑的每个硬件方面 ...
- 02_Linux实操篇
第五章 VI和VIM编辑器 5.1. VI和VIM基本介绍 Vi编辑器是所有Unix及Linux系统下标准的编辑器,它的强大不逊色于任何最新的文本编辑器.由于对Unix及Linux系统的任何版本,Vi ...
- IntelliJ IDEA 2020.2正式发布,诸多亮点总有几款能助你提效
向工具人致敬.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众号[BAT的乌托邦]逐 ...
- pandas_重采样多索引标准差协方差
# 重采样 多索引 标准差 协方差 import pandas as pd import numpy as np import copy # 设置列对齐 pd.set_option("dis ...
- Python os.open() 方法
概述 os.open() 方法用于打开一个文件,并且设置需要的打开选项,模式参数mode参数是可选的,默认为 0777.高佣联盟 www.cgewang.com 语法 open()方法语法格式如下: ...
- 记录一次线上实施snmp
公司要实施一个部级的项目,我们公司的提供的产品要对接下客户的一个平台监控平台,该监控平台使用snmp,我们公司的产品不支持snmp,所以由我负责在现网实施snmp,记录这次现网 一.生成编译规则 1. ...
- 笨办法学习python3练习代码:argv参数变量与文件操作
ex15.py 完成ex15.py需要在ex15.py同文件夹目录下面准备一个txt文件(ex15_sample.txt) 执行ex15.py 如: python ex15.py e ...
- Core下简易WebApi
代码很粗糙~ 粘贴github地址 https://github.com/htrlq/MiniAspNetCoreMini demo public class Startup { public Sta ...