最小生成树(Prim算法)

先来介绍一下什么是最小生成树。

概述

在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此的权重,若存在 T 为 E 的子集且为无循环图,使得联通所有结点的的 w(T) 最小,则此 T 为 G 的最小生成树

最小生成树其实是最小权重生成树的简称。

最小生成树图解简述:

由图可知,给了n个点,m条边的无向联通图,求最小生成树的值。

根据最小生成树的特性:它一定有n个点和n-1条边

它最终最小生成树的图解:其值为8(大家可以试试其他的生成树,最终最小的生成树就是如下)

 

应用:

生成树和最小生成树有许多重要的应用。

例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

证明(略):(大佬们应该关注的是如何运用,当然了解证明过程是最好的,可惜本蒟蒻不会讲解其证明...-q_q-)

本篇对Prim算法的讲解主要采用例题+代码形式进行讲解,因为只有结合代码才能更好的理解,否则容易沦为空谈.....

废话不多说,上题上代码!

例题链接:http://oj.daimayuan.top/course/14/problem/690

 代码实现:

//Prim算法跟Dijkstra算法很像,唯一不同的是多了一个bool数组和距离更换的方式不一样(请务必注意不同!!!)
//注:如果题目告知 该图是联通图,则可以把while(1)改为循环N次,tot计数给去掉,因为联通图本身就具备这些性质,当然仍写比较全面
//裸的Prim算法:O(n^2+m)
//算法思路:
//1:先任意加入一个点进行Prim运算(假设已经完成了初始化操作)
//2:取不在集合C中的最小点进入集合C中,并以该点延伸出去的点来更新不在集合C中的点。依次操作:
//3:最终在集合C中的点值,就是最小生成树的值(集合C中的点集和)
#include<bits/stdc++.h>//万能头文件,嘻嘻嘻...
using namespace std;
const int N=1001;
struct Node//定义一个到点y且边权(距离)为v的结构体
{
	int y,v;
	Node(int _y,int _v)//重构函数,不懂的,照抄就行
	{
		y=_y,v=_v;
	};
};
vector<Node>e[N];//vector容器用来存储图
bool b[N];//用bool数组表示是否在集合C里
int n,m,dist[N];//dist数组表示当前该点的距离
//(未被堆优化的)Prim算法
void Prim()
{
	memset(b,false,sizeof(b));//初始b数组全为false,表示所有点都不在集合C里
	memset(dist,127,sizeof(dist));//初始所有距离都为无穷大,表示所有点都不联通(或者说所有点都是单独的点)
	dist[1]=0;//从任意一个点开始(可以是dist[2],dist[3]...)
	int ans=0,tot=0;//用ans记录目前最小生成树的值,tot表示有多少点在集合C里
	while(1)
	{
		int x=-1;    //x代表不在集合C中最小边权的点
		for(int i=1;i<=n;i++)//枚举每一个点
		if(!b[i]&&dist[i]<1<<30)//如果该点不在集合C里且不是单独的点
		if(x==-1||dist[i]<dist[x])//且存在一个点的距离小于当前点的距离
		x=i;//x取进入集合C的点
		
		if(x==-1) break;//如果枚举完所有的点都没有找到可以进入C的点,就结束循环
		++tot;
		ans+=dist[x];//求最小生成树的和
		b[x]=true;//说明当前点x在集合C里
		//由于C++11的特性,这个循环操作的意思为,枚举e[x]里面所有的点
		for(auto i:e[x])//延伸点位进行更新操作
		dist[i.y]=min(dist[i.y],i.v);
	}
	if(tot!=n) printf("-1");//如果所有点不在集合C里面,则不能生成最小生成树
	else printf("%d",ans);//否则输出最小生成树的值
}
int main()
{
	scanf("%d%d",&n,&m);//输入n个点,m条边
	for(int i=1;i<=m;i++)//进行构图操作
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);//双向边操作
		e[x].push_back(Node(y,z));//表示为:从x到y的边权为z
		e[y].push_back(Node(x,z));//表示为:从y到x的边权为z
	}
	Prim();//调用函数
}

 附录:本篇blog并没有加堆优化Prim的版本,其实本蒟蒻觉得堆优化的Prim存在许多不足,优化后的Prim并不是万能的,它有时候跑的还比未优化版本的慢,其次虽然Prim(优化)的复杂的为O((m+n)logn)的,但是当n非常大的时候,其复杂度就会变为O(n^2logn)当然就比O(n^2+m)的复杂度高。

然后就有人会问了,那么遇到这种情况该怎么办呢?优化都过不了,那不就g了?其实,Kruskal算法可以解决Prim(堆优化)的问题,且Kruskal的复杂度为O(mlogm)还比Prim(堆优化)好写,那么为什么不采用Kruskal捏?

后面我会更新Kruskal算法,望各位巨佬们莅临...(蒟蒻先溜了...0.0)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值