最小生成树

本篇简单总结最小生成树最经典的两种k算法和p算法。

0.前置

首先介绍着两个算法之前先介绍一下问题背景,所谓最小生成树就是在图中保证连通性的情况下,所有边权值总和的最小值,问题模板:洛谷P3366 最小生成树

1.k算法

k算法是写起来最简单也是最常用的最小生成树算法,其简单的算法逻辑如下

首先对所有的边进行排序,当然要对边进行编号并且记录每条边链接的节点个数,此后从最小的边开始,依次进行判断,如果加入这条边会不会成环,其实也就是判断该边对应的两个点是不是在已经形成的连通图中,所以我们就需要用并查集辅助判断,如果可以加入就把连通的所有点合并为一个集合,不行则跳过,直到加入了n-1条边(n为节点个数,n-1条边就是形成无环连通图的最小边数)后便得到了最小生成树。(如果边不足n-1说明图无法连通)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
bool cmp(vector<int> a,vector<int> b){
	return a[0]<b[0];
}
int find(vector<int>& f,int i){
	if(i!=f[i]){
		f[i]=find(f,f[i]);
	}
	return f[i];
}
bool U(vector<int>& f,int i,int j){
	i=find(f,i),j=find(f,j);
	if(i!=j){
		f[i]=j;
		return true;
	}else return false;
}
void solve(){
		int n,m;//代表元素个数
		cin>>n>>m;
	   vector<int> f(n+1);
	   vector<vector<int>> graph(m,vector<int>(3));
	   for(int i=0;i<n;i++) f[i]=i;
	   
	   for(int i=0;i<m;i++){
	   	cin>>graph[i][1]>>graph[i][2]>>graph[i][0];
	   }
	   sort(graph.begin(),graph.end(),cmp);
	   int cnt=0;
	   int ans=0;
	   for(int i=0;i<m;i++){
	   	if(U(f,graph[i][1],graph[i][2])){
	   		cnt++;
	   		ans+=graph[i][0];
	   		}
	   	if(cnt==n-1) break;
	   }
	   if(cnt==n-1){
	   cout<<ans<<endl;
	   }else cout<<"orz"<<endl;
}
int main()
{
	 solve();
	return 0;
}

2.p算法

p算法需要用到堆结构,还需要一个set结构用来查询那些点已经去过,首先加入头节点的所有边,此后从堆里弹出一个最小的边,查看这条边去往的节点是否已经去过,如果去过就放弃这条边,继续弹出下一条边,直到找到了去往一个没去过的节点的边,把这条边作为答案的一部分,然后加入去往的后续节点的所有边,再重复上述步骤,在循环过程中,一旦出现堆为空或者边的数量达到n-1,就结束循环,最后如果边的数量不足就说明无法连通,否则得到答案。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
bool cmp(vector<int>& a,vector<int>& b){
	return a[1]>b[1];
}
void solve(){
		int n,m;
		cin>>n>>m;
		vector<vector<vector<int>>> graph(n+1);
		for(int i=0;i<m;i++){
			int a,b,w;
			cin>>a>>b>>w;
			graph[a].push_back({b,w});
			graph[b].push_back({a,w});
		}
		priority_queue<vector<int>,vector<vector<int>>,decltype(&cmp)> heap(&cmp);
		set<int> st;
		vector<int> ei(2);
		int ans=0,cnt=1;
		for(auto it=graph[1].begin();it!=graph[1].end();it++){
			heap.push(*it);
		}
		st.insert(1);
		while(!heap.empty()){
			ei=heap.top();
			heap.pop();
			if(!st.count(ei[0])){
				cnt++;
				ans+=ei[1];
				st.insert(ei[0]);
			for(auto it=graph[ei[0]].begin();it!=graph[ei[0]].end();it++){
			heap.push(*it);
		}
			}
		}
		if(cnt==n){
			cout<<ans<<endl;
		}else cout<<"orz"<<endl;
}
int main()
{
	 solve();
	
	return 0;
}

上面两种算法的复杂度都是o(n+m)+o(m*logm)

还有一种p算法的用反向索引优化的解法,当边的数量很大而点的数量相对少的时候可以进行很好的时间优化。懒了...过段时间来补。

一道例题:leetcode.水资源分配优化。

简单来说就是有一些镇子,每个镇子都需要水源,而每个镇子有两种选择方式,可以自己选择建一个水源净化装置,花费存在一个数组中(不同村子花费不同),第二种方式可以选择与别的村子之间修路,用别的村子的水,求所有村子都可以得到水源的最小花费。

其实可以把建净化装置的代价看作一个水源地,修到不同村子的代价,最后就变成了最小生成树问题,代码细节就在于建图(其实k算法不用建图),把修净化装置代价抽象成边,最后一起清算即可。

至此。(根据网络资料总结)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值