洛谷P3366 最小生成树板子题

这篇博客介绍两个算法:Prim算法和Kruskal算法,两个算法各有优劣

一般来说当图比较稀疏的时候,Kruskal算法比较快

而当图很密集,Prim算法就大显身手了

下面是这两种算法的介绍


Prim算法

百度百科定义:传送门

好吧,其实当我第一眼看到这个东西的时候感觉和Dijkstra好像,但是学了之后发现其实区别还是很明显(并且好记)的

Dijkstra是维护从到源点的最短长度,而Prim则是维护到最小生成树的最短长度(其实就是到最小生成树上所有点的最短长度)

那么Prim到底是什么呢?

Prim的思想是将任意节点作为根,再找出与之相邻的所有边(用一遍循环即可),再将新节点更新并以此节点作为根继续搜,维护一个数组:dis,作用为已用点到未用点的最短距离。

Prim算法之所以是正确的,主要基于一个判断:对于任意一个顶点v,连接到该顶点的所有边中的一条最短边(v, vj)必然属于最小生成树(即任意一个属于最小生成树的连通子图,从外部连接到该连通子图的所有边中的一条最短边必然属于最小生成树)

举个栗子:

STEP 1

此为原始的加权连通图。每条边一侧的数字代表其权值。

STEP 2

顶点D被任意选为起始点。顶点A、B、E和F通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。

STEP 3

下一个顶点为距离D或A最近的顶点。B距D为9,距A为7,E为15,F为6。因此,F距D或A最近,因此将顶点F与相应边DF以高亮表示。

STEP 4

算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。

STEP 5

在当前情况下,可以在C、E与G间进行选择。C距B为8,E距B为7,G距F为11。点E最近,因此将顶点E与相应边BE高亮表示。

STEP 6

这里,可供选择的顶点只有C和G。C距E为5,G距E为9,故选取C,并与边EC一同高亮表示。

STEP 7

顶点G是唯一剩下的顶点,它距F为11,距E为9,E最近,故高亮表示G及相应边EG。

STEP 8

现在,所有顶点均已被选取,图中绿色部分即为连通图的最小生成树。在此例中,最小生成树的权值之和为39。

复杂度:

这里记顶点数v,边数e
邻接矩阵:O(v) 邻接表:O(elog2v)

下面是代码及注释:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<time.h>
#include<queue>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pr;
const double pi=acos(-);
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define Rep(i,u) for(int i=head[u];i;i=Next[i])
#define clr(a) memset(a,0,sizeof a)
#define pb push_back
#define mp make_pair
#define fi first
#define sc second
ld eps=1e-;
ll pp=;
ll inf=;
#define maxn 5005
#define maxm 200005
ll mo(ll a,ll pp){if(a>= && a<pp)return a;a%=pp;if(a<)a+=pp;return a;}
ll powmod(ll a,ll b,ll pp){ll ans=;for(;b;b>>=,a=mo(a*a,pp))if(b&)ans=mo(ans*a,pp);return ans;}
ll read(){
ll ans=;
char last=' ',ch=getchar();
while(ch<'' || ch>'')last=ch,ch=getchar();
while(ch>='' && ch<='')ans=ans*+ch-'',ch=getchar();
if(last=='-')ans=-ans;
return ans;
}
//head struct edge
{
int to,_dis,next;////出边的终点、出边的长度、下一条出边
}edge[maxm<<];//因为是无向图,所以开双倍数组,双倍快乐 int head[maxn],dis[maxn],cnt,n,m,tot,now=,ans;
//dis数组表示当前点到最小生成树的最短路径
bool vis[maxn]; inline void add_edge(int from,int to,int value)
{
edge[++cnt].to=to;
edge[cnt]._dis=value;
edge[cnt].next=head[from];
head[from]=cnt;
}//添加边 inline int prim()
{
rep(i,,n)
dis[i]=inf;//初始化
for(int i=head[];i;i=edge[i].next)//遍历当前节点的每一条出边
dis[edge[i].to]=min(dis[edge[i].to],edge[i]._dis);//重边の处理
while(++tot<n)//就是tot<=n-1,因为最小生成树的边数一定等于节点数-1
{
int minn=inf;//初始化min
vis[now]=;//已经到达
rep(i,,n)
if(!vis[i]&&minn>dis[i])//寻找到最小生成树距离最短的节点
minn=dis[i],now=i;//更新
ans+=minn;//更新最小生成树
for(int i=head[now];i;i=edge[i].next)//遍历每一条出边
{
int to=edge[i].to;
if(dis[to]>edge[i]._dis&&!vis[to])
dis[to]=edge[i]._dis;//更新dis数组
} }
return ans;
}
int main()
{
n=read(),m=read();
rep(i,,m)
{
int from=read(),to=read(),value=read();
add_edge(from,to,value);//因为是无向图
add_edge(to,from,value);//双倍存储,双倍快乐
}
cout<<prim();
}

Kruskal算法

Kruskal算法的思想比Prim好理解一些。先把边按照权值进行排序,用贪心的思想优先选取权值较小的边,并依次连接,若出现环则跳过此边(用并查集来判断是否存在环)继续搜,直到已经使用的边的数量比总点数少一即可。

证明:刚刚有提到:如果某个连通图属于最小生成树,那么所有从外部连接到该连通图的边中的一条最短的边必然属于最小生成树。所以不难发现,当最小生成树被拆分成彼此独立的若干个连通分量的时候,所有能够连接任意两个连通分量的边中的一条最短边必然属于最小生成树

上面提到:这个东西要用到“并查集”

不了解的:传送门

代码及注释:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<time.h>
#include<queue>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pr;
const double pi=acos(-);
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define Rep(i,u) for(int i=head[u];i;i=Next[i])
#define clr(a) memset(a,0,sizeof a)
#define pb push_back
#define mp make_pair
#define fi first
#define sc second
ld eps=1e-;
ll pp=;
ll inf=;
#define maxn 5005
#define maxm 200005
ll mo(ll a,ll pp){if(a>= && a<pp)return a;a%=pp;if(a<)a+=pp;return a;}
ll powmod(ll a,ll b,ll pp){ll ans=;for(;b;b>>=,a=mo(a*a,pp))if(b&)ans=mo(ans*a,pp);return ans;}
ll read(){
ll ans=;
char last=' ',ch=getchar();
while(ch<'' || ch>'')last=ch,ch=getchar();
while(ch>='' && ch<='')ans=ans*+ch-'',ch=getchar();
if(last=='-')ans=-ans;
return ans;
}
//head struct Edge
{
int from,to,_dis;
}edge[maxm];
int fa[maxn],n,m,ans,eu,ev,cnt;
//father数组用来存储父亲节点
bool cmp(Edge a,Edge b)
{
return a._dis<b._dis;//比较函数
}
inline int find_die(int x)
{
while(x!=fa[x]) x=fa[x]=fa[fa[x]];
return x;//找爹
}
inline int kruskal()
{
sort(edge+,edge++m,cmp);//先将所有的边按权值排序
rep(i,,m)
{
eu=find_die(edge[i].from);
ev=find_die(edge[i].to);//分别找始点和终点的祖宗节点
if(eu==ev)//如果是一个祖宗,就说明他们在一个联通图中
continue;
ans+=edge[i]._dis;//更新最小生成树长度
fa[ev]=eu;//顺便标记父亲
if(++cnt==n-)//知道生成最小生成树
break;
}
return ans;
} int main()
{
n=read(),m=read();
rep(i,,n)
fa[i]=i;//初始化自己是自己的父亲
rep(i,,m)
edge[i].from=read(),edge[i].to=read(),edge[i]._dis=read();
cout<<kruskal();
}

加上判断无解的情况:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<time.h>
#include<queue>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pr;
const double pi=acos(-);
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define Rep(i,u) for(int i=head[u];i;i=Next[i])
#define clr(a) memset(a,0,sizeof a)
#define pb push_back
#define mp make_pair
#define fi first
#define sc second
ld eps=1e-;
ll pp=;
ll inf=;
#define maxn 500001
#define maxm 1000001
ll mo(ll a,ll pp){if(a>= && a<pp)return a;a%=pp;if(a<)a+=pp;return a;}
ll powmod(ll a,ll b,ll pp){ll ans=;for(;b;b>>=,a=mo(a*a,pp))if(b&)ans=mo(ans*a,pp);return ans;}
ll read(){
ll ans=;
char last=' ',ch=getchar();
while(ch<'' || ch>'')last=ch,ch=getchar();
while(ch>='' && ch<='')ans=ans*+ch-'',ch=getchar();
if(last=='-')ans=-ans;
return ans;
}
//head struct Edge
{
int from,to,_dis;
}edge[maxm];
int fa[maxn],n,m,ans,eu,ev,cnt;
//father数组用来存储父亲节点
bool cmp(Edge a,Edge b)
{
return a._dis<b._dis;//比较函数
}
inline int find_die(int x)
{
while(x!=fa[x]) x=fa[x]=fa[fa[x]];
return x;//找爹
}
int main()
{
n=read(),m=read();
rep(i,,n)
fa[i]=i;//初始化自己是自己的父亲
rep(i,,m)
edge[i].from=read(),edge[i].to=read(),edge[i]._dis=read();
sort(edge+,edge++m,cmp);//先将所有的边按权值排序
rep(i,,m&&cnt<=n-)
{
eu=find_die(edge[i].from);
ev=find_die(edge[i].to);//分别找始点和终点的祖宗节点
if(eu==ev)//如果是一个祖宗,就说明他们在一个联通图中
continue;
ans+=edge[i]._dis;//更新最小生成树长度
fa[ev]=eu;//顺便标记父亲
cnt++;
}
if(cnt!=n-)
cout<<"orz";
else
cout<<ans;
}

 码字不易,如果看的满意请点个推荐哦~

最小生成树——Prim算法和Kruskal算法的更多相关文章

  1. 转载:最小生成树-Prim算法和Kruskal算法

    本文摘自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html 最小生成树-Prim算法和Kruskal算法 Prim算 ...

  2. 最小生成树Prim算法和Kruskal算法

    Prim算法(使用visited数组实现) Prim算法求最小生成树的时候和边数无关,和顶点树有关,所以适合求解稠密网的最小生成树. Prim算法的步骤包括: 1. 将一个图分为两部分,一部分归为点集 ...

  3. 最小生成树---Prim算法和Kruskal算法

    Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (gra ...

  4. 最小生成树Prim算法和Kruskal算法(转)

    (转自这位大佬的博客 http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html ) Prim算法 1.概览 普里姆算法(Pr ...

  5. hdu1233 最小生成树Prim算法和Kruskal算法

    Prim算法 时间复杂度:O(\(N^2\),N为结点数) 说明:先任意找一个点标记,然后每次找一条最短的两端分别为标记和未标记的边加进来,再把未标记的点标记上.即每次加入一条合法的最短的边,每次扩展 ...

  6. 最小生成树之Prim算法和Kruskal算法

    最小生成树算法 一个连通图可能有多棵生成树,而最小生成树是一副连通加权无向图中一颗权值最小的生成树,它可以根据Prim算法和Kruskal算法得出,这两个算法分别从点和边的角度来解决. Prim算法 ...

  7. java实现最小生成树的prim算法和kruskal算法

    在边赋权图中,权值总和最小的生成树称为最小生成树.构造最小生成树有两种算法,分别是prim算法和kruskal算法.在边赋权图中,如下图所示: 在上述赋权图中,可以看到图的顶点编号和顶点之间邻接边的权 ...

  8. 【数据结构】最小生成树之prim算法和kruskal算法

    在日常生活中解决问题经常需要考虑最优的问题,而最小生成树就是其中的一种.看了很多博客,先总结如下,只需要您20分钟的时间,就能完全理解. 比如:有四个村庄要修四条路,让村子能两两联系起来,这时就有最优 ...

  9. Prim算法和Kruskal算法

       Prim算法和Kruskal算法都能从连通图找出最小生成树.区别在于Prim算法是以某个顶点出发挨个找,而Kruskal是先排序边,每次选出最短距离的边再找. 一.Prim(普里姆算法)算法: ...

随机推荐

  1. KVM虚拟化使用详解--技术流ken

    KVM介绍 Kernel-based Virtual Machine的简称,是一个开源的系统虚拟化模块,自Linux 2.6.20之后集成在Linux的各个主要发行版本中. KVM的虚拟化需要硬件支持 ...

  2. Asp.net MVC 中 CodeFirst 开发模式实例

    昨天写的这篇博客因为下班时间到了忘记保存了,好郁闷,得重新写一遍.实习所在公司使用的是CodeFirst开发模式,最近开始参与到公司的项目里面来了,发现这个模式特别好用,建库建表改变字段属性添加删除字 ...

  3. .net core jwt 入门记录

    从百度里搜索里搜索了很多jwt的文章,跟着文章写了一个demo,这里记录下学习过程中碰上的问题.看文章多遍,不如手工实现一次. 模板已上传到github.com:dogvane/webapi_jwt_ ...

  4. linux服务器运维管理学习

    一. 了解linux 1.Linux操作系统是基于UNIX操作系统发展而来的一种克隆系统,它诞生于1991 年的 [Linux桌面] 10 月5 日(这是第一次正式向外公布的时间).以后借助于Inte ...

  5. Kafka学习资料

    博客系列: Apache Kafka简介Apache Kafka安装和使用Apache Kafka核心概念kafka核心组件和流程—控制器kafka核心组件和流程—协调器kafka核心组件和流程—日志 ...

  6. Struts2笔记_拦截器

    A.拦截器是什么 --- Interceptor:拦截器,起到拦截Action的作用. ---Filter:过滤器,过滤从客户端向服务器发送的请求. ---Interceptor:拦截器,拦截是客户端 ...

  7. OPPO A7x在哪里开启usb调试模式的详细经验

    当我们使用Pc连接安卓手机的时候,如果手机没有开启Usb调试模式,Pc则没法成功读到我们的手机,这时我们需要想办法将手机的Usb调试模式开启,这里我们讲解OPPO A7x如何开启Usb调试模式的方法. ...

  8. QT之setstylesheet防止子窗体继承父窗体样式

    /* 1.这里的#号表示,主控件不会影响子控件 2.设置多个样式,可以用双引号和分号 */ ui->groupBox_1->setStyleSheet("#groupBox_1{ ...

  9. django 时区和系统(ubuntu)时区修改

    django时区默认使用UTC,中国人使用CST东八区. settings.py改为上海时区 #settings.py TIME_ZONE = 'Asia/Shanghai' # True:使用UTC ...

  10. Windows Server(r12) - 配置 MySQL 远程访问

    Windows Server(r12) - 配置 MySQL 远程访问 工作主要为两部分, 一部分是 Windows 防火墙, 一部分是 MySQL 自身 Windows 端口远程访问 其实就是在 W ...