【图论】最小生成树|Prim+Kruskal算法

文章目录

最小生成树

最小生成树的算法:Prim算法和Kruskal算法

定义

适用场景:无向带权连通图

有向图没有最小生成树

最小生成树:在所有生成树里面,所有边权加起来总和最小的那一棵

选取n-1条边使得图中所有节点连接到一起,并且边的权值和最小

核心性质

  1. 所有点全部连通
  2. 整张图无环
  3. 边权总和最小
  4. MST 可以存在负权边,算法照样正常工作,这点和最短路不一样。

Prim算法(加点法)

适用于稠密图

每次寻找距离最小生成树最近的节点并加入到最小生成树中。prim算法核心就是以下三步:

  1. 选距离生成树最近非生成树节点
  2. 最近节点加入生成树
  3. 更新非生成树节点到生成树的距离(ans)

ans数组用来记录每一个节点距离最小生成树的最近距离

模板

朴素版(邻接矩阵+遍历)
cpp 复制代码
//orz orz orz
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define ull unsigned long long
#define fi first
#define se second
#define PLL pair<ll, ll>
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
const ll MAXN=0x3f3f3f3f3f3f3f3f;
const ll mod=1e9+7;
using namespace std;
ll v,e;
ll x,y,k;

int main()
{
	IOS
	cin>>v>>e;
	vector<vector<ll>>g(v+1,vector<ll>(v+1,MAXN));
    //邻接矩阵存无向图
	while(e--)
	{
		cin>>x>>y>>k;
		g[x][y]=k;
		g[y][x]=k;
	}	
	vector<ll>ans(v+1,MAXN);// 所有节点到最小生成树的最小距离
	vector<bool>inT(v+1,false); // 这个节点是否在树里
	ans[1]=0;
    // 只需要循环 n-1次,建立 n - 1条边就可以把n个节点的图连在一起
	for(ll i=1;i<=v-1;i++) 
	{
        //选距离生成树最近节点
		ll id=-1;
		ll minv=LLONG_MAX;
		for(ll j=1;j<=v;j++)
		{
            //  选取最小生成树节点的条件:
            //  (1)不在最小生成树里
            //  (2)距离最小生成树最近的节点
			if(!inT[j]&&ans[j]<minv)
			{
				minv=ans[j];
				id=j;
			}
		}
        //最近节点(id)加入生成树
		inT[id]=true;
		for(ll j=1;j<=v;j++)
		{
            //更新非生成树节点到生成树的距离(即更新ans数组)
            // 更新的条件:
            // (1)节点是非生成树里的节点
            // (2)与id相连的某节点的权值比该某节点距离最小生成树的距离小
			if(!inT[j]&&g[id][j]<ans[j])
			{
				ans[j]=g[id][j];
			}
		}
	}
	ll res=0;
	for(ll i=2;i<=v;i++)
	{
		res+=ans[i];
	}
	cout<<res<<endl;

//	cout<<fixed<<setprecision(x)<< ;
	return 0;
}
堆优化版(邻接矩阵+优先队列)
cpp 复制代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define ull unsigned long long
#define fi first
#define se second
#define PLL pair<ll, ll>
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
const ll MAXN=11000;
const ll inf=0x3f3f3f3f;
using namespace std;
ll n,m;
int main()
{
	IOS
	cin>>n>>m;
	vector<vector<PLL>>e(n+1);
	vector<bool>vis(n+1,false);
	vector<ll>dis(n+1,inf);
	priority_queue<PLL,vector<PLL>,greater<PLL>>q;
	for(ll i=1;i<=m;i++)
	{
		ll u,v,w;
		cin>>u>>v>>w;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	dis[1]=0;
	q.push({0,1});
	ll res=0,cnt=0;
	while(!q.empty()&&cnt<n)
	{
		auto [w,u]=q.top();
		q.pop();
		if(vis[u])
		{
			continue;
		}
		vis[u]=true;
		res+=w;
		cnt++;
		for(auto [v,w]:e[u])
		{
			if(!vis[v]&&w<dis[v])
			{
				dis[v]=w;
				q.push({w,v});
			}
		}
	}
	cout<<(cnt==n?res:-1)<<endl;
	
//	cout<<fixed<<setprecision(x)<< ;
	return 0;
}

Kruskal算法(加边法)

适用于稀疏图

都是贪心思路

Kruskal算法和Prim算法解决的是同一个问题,两者区别在于prim算法是以点构建最小生成树的,而kruskal算法则是通过边和边构成最小生成树的

具体步骤

  • 边的权值排序,因为要优先选最小的边加入到生成树里
  • 遍历排序后的边
    • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环
    • 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合

个人感觉 这么写下来还是 Kruskal 比Prim好理解 借助并查集就非常好理解 而且代码简单对并查集不太熟悉的可以戳这里【图论】并查集

模板

cpp 复制代码
//orz orz orz
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define ull unsigned long long
#define fi first
#define se second
#define PLL pair<string, ll>
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
const ll MAXN=1100;
const ll mod=1e9+7;
using namespace std;
struct e{
	ll l,r,v;
};
bool cmp(const e &a,const e &b)
{
	return a.v<b.v;
}
ll p[MAXN];
void in(ll n)
{
	for(ll i=1;i<=n;i++)
	{
		p[i]=i;
	}
}
ll Find(ll x)
{
	if(p[x]==x)
	{
		return x;
	}
	else
	{
		return p[x]=Find(p[x]);
	}
}
void u(ll x,ll y)
{
	ll nx=Find(x);
	ll ny=Find(y);
	if(nx!=ny)
	p[nx]=ny;
}
bool same(ll u,ll v)
{
	u=Find(u);
	v=Find(v);
	return u==v;
}
ll vv,ee;
ll l1,r1,v1;
ll res=0;
int main()
{
	IOS
	cin>>vv>>ee;
	vector<e>ed;
	while(ee--)
	{
		cin>>l1>>r1>>v1;
		ed.push_back({l1,r1,v1});
	}
	sort(ed.begin(),ed.end(),cmp);
	in(vv);
	for(e ede:ed)
	{
		ll x=Find(ede.l);
		ll y=Find(ede.r);
		if(x!=y)
		{
			res+=ede.v;
			u(x,y);
		}
	}
	cout<<res<<endl;
//	cout<<fixed<<setprecision(x)<< ;
	return 0;
}
Prim Kruskal
核心思想 加点,维护一个已经建好的生成树集合,每次选离集合最近的点加入 加边,把所有边从小到大排序,不断选最小的边,不能形成环
数据结构 朴素 Prim:邻接矩阵堆优化 Prim:邻接表 + 小根堆 邻接表存边 + 并查集 (DSU)
时间复杂度 朴素 O(n2) 堆优化 O(mlogn) O(mlogm) 主要开销是边排序
适合图 稠密图(点少边多)用朴素 Prim稀疏图用堆优化 Prim 稀疏图(边数少)首选 Kruskal,代码短好写
处理连通判断 统计加入 MST 的点数量cnt == n 统计选中边数cnt == n‑1
图不连通 输出‑1 输出‑1

Kruskal 一定要写路径压缩并查集,否则会 TLE

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

模板题

53. 寻宝

完完全全就是一个模板题两种模板随便一个 就够用

cpp 复制代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define ull unsigned long long
#define fi first
#define se second
#define PLL pair<ll, ll>
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
const ll MAXN=0x3f3f3f3f3f3f3f3f;
const ll mod=1e9+7;
using namespace std;
ll v,e;
ll x,y,k;

int main()
{
	IOS
	cin>>v>>e;
	vector<vector<ll>>g(v+1,vector<ll>(v+1,MAXN));
	while(e--)
	{
		cin>>x>>y>>k;
		g[x][y]=k;
		g[y][x]=k;
	}	
	vector<ll>ans(v+1,MAXN);
	vector<bool>inT(v+1,false);
	ans[1]=0;
	for(ll i=1;i<=v-1;i++)
	{
		ll id=-1;
		ll minv=LLONG_MAX;
		for(ll j=1;j<=v;j++)
		{
			if(!inT[j]&&ans[j]<minv)
			{
				minv=ans[j];
				id=j;
			}
		}
		inT[id]=true;
		for(ll j=1;j<=v;j++)
		{
			if(!inT[j]&&g[id][j]<ans[j])
			{
				ans[j]=g[id][j];
			}
		}
	}
	ll res=0;
	for(ll i=2;i<=v;i++)
	{
		res+=ans[i];
	}
	cout<<res<<endl;

//	cout<<fixed<<setprecision(x)<< ;
	return 0;
}
相关推荐
charlie11451419116 分钟前
Cinux · musl 静态移植:对齐 Linux ABI、铺初始栈,以及一个被 SMAP 拦下的潜伏 bug
linux·开发语言·c++·操作系统·开源项目
_Narcissus_26 分钟前
单调栈笔记及例题详解
数据结构·c++·笔记·算法·力扣·单调栈·洛谷
流浪00131 分钟前
C/C++后端筑基系列(二):C++ 从基础到进阶
开发语言·c++
kyle~33 分钟前
x86 汇编LOCK前缀 --- 硬件架构向软件提供的核心同步原语
汇编·c++·性能优化·硬件架构·实时系统
额,不知道写啥。1 小时前
从区间加一次函数到区间加多次函数最值-----区间数据结构与函数的一些东西(《浅谈函数最值的动态维护》的学习笔记)
数据结构·笔记·学习
LuminousCPP1 小时前
数据结构-二叉树(五):查找、销毁与前序序列建树
c语言·数据结构·笔记·二叉树
Escalating_xu2 小时前
【C++入门基础(下)】默认参数、函数重载、引用、inline 与 nullptr
android·c++·redis
艾莉丝努力练剑3 小时前
【AI大模型接入SDK】Provider分析与实现
c++·人工智能·学习·面试·大模型·llm