本篇简单总结最小生成树最经典的两种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算法不用建图),把修净化装置代价抽象成边,最后一起清算即可。
至此。(根据网络资料总结)