最小生成樹和最大生成樹
1 生成樹概念
生成樹(spanning tree) :一個連通無向圖的生成子圖,同時要求是樹。也即在圖的邊集中選擇n-1條,將所有頂點連通。
2 最小生成樹
2.1 定義
最小生成樹為一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。最小生成樹可以用kruskal(克魯斯卡爾)算法或prim(普里姆)算法求出。
我們定義無向連通圖的 最小生成樹 (Minimum Spanning Tree,MST)為邊權和最小的生成樹。
例題(洛谷):【模板】最小生成樹
2.2 kruskal實現
Kruskal 算法是一種常見並且好寫的最小生成樹算法,由 Kruskal 發明。該算法的基本思想是從小到大加入邊,是個貪心算法。
#include<bits/stdc++.h>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
const int N=2e5+5;
int n,m;
ll ans;
struct edge{
int u,v,w;
}e[N];
int f[5005],add=1;
bool cmp(edge a,edge b){return a.w<b.w;}
inline int find(int k)
{
if(f[k]==k)return k;
else return f[k]=find(f[k]);
}
inline void link(int a,int b)
{
f[find(b)]=find(a);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
for(int i=0;i<5005;i++)f[i]=i;
cin>>n>>m;
for(int i=0;i<m;i++){
int a,b,c;
cin>>a>>b>>c;
e[i]=(edge){a,b,c};
}
sort(e,e+m,cmp);
for(int i=0;i<m&&add<=n;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
if(find(u)!=find(v)){
link(u,v);
ans+=w;
add++;
}
}
if(add!=n)cout<<"orz";//不連通
else cout<<ans;
return 0;
}
2.3 prim實現
Prim 算法是另一種常見並且好寫的最小生成樹算法。該算法的基本思想是從一個結點開始,不斷加點(而不是 Kruskal 算法的加邊)。
實現:
具體來說,每次要選擇距離最小的一個結點,以及用新的邊更新其他結點的距離。
其實跟 Dijkstra 算法一樣,每次找到距離最小的一個點,可以暴力找也可以用堆維護。
堆優化的方式類似 Dijkstra 的堆優化,但如果使用二叉堆等不支持 decrease-key 的堆,復雜度就不優於 Kruskal,常數也比 Kruskal 大。所以,一般情況下都使用 Kruskal 算法,在稠密圖尤其是完全圖上,暴力 Prim 的復雜度比 Kruskal 優,但 不一定 實際跑得更快。
還未進行代碼實現,之后補上。
3 最大生成樹
3.1 定義
和最小生成樹類似,只不過在一個圖的所有生成樹中邊權值和最大的生成樹即為最大生成樹。
例題為學校DS網站上的題目,和最小生成樹的題目差不多。
3.2 實現
1、將圖中所有邊的邊權變為相反數,再跑一遍最小生成樹算法。相反數最小,原數就最大。
2、修改一下最小生成樹算法:對於kruskal,將“從小到大排序”改為“從大到小排序”;
對於prim,將“每次選到所有藍點代價最小的白點”改為“每次選到所有藍點代價最大的點”
kruskal實現代碼
- 將邊權從大到小排序。
/*用Kruskal實現最大生成樹*/
/*將邊權從大到小排序的Kruskal*/
#include<bits/stdc++.h>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m;
ll ans;
struct edge{
int u,v,w;
}e[N];
int f[1005],add=1;
bool cmp(edge a,edge b){return a.w>b.w;}
inline int find(int k)
{
if(f[k]==k)return k;
else return f[k]=find(f[k]);
}
inline void link(int a,int b)
{
if(find(a)!=find(b))f[find(b)]=find(a);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
for(int i=0;i<1005;i++)f[i]=i;
cin>>n>>m;
for(int i=0;i<m;i++){
int a,b,c;
cin>>a>>b>>c;
if(a>b)swap(a,b);
e[i]=(edge){a,b,c};
}
sort(e,e+m,cmp);
for(int i=0;i<m&&add<=n;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
if(find(u)!=find(v)){
link(u,v);
ans+=w;
add++;
}
}
if(add!=n)cout<<-1;
else cout<<ans;
return 0;
}
- 或者將每個邊的權值變成相反數,跑一遍最小生成樹。
/*用Kruskal實現最大生成樹*/
/*或者將每個邊的權值變成相反數,跑一遍最小生成樹*/
#include<bits/stdc++.h>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m;
ll ans;
struct edge{
int u,v,w;
}e[N];
int f[1005],add=1;
bool cmp(edge a,edge b){return a.w<b.w;}
inline int find(int k)
{
if(f[k]==k)return k;
else return f[k]=find(f[k]);
}
inline void link(int a,int b)
{
if(find(a)!=find(b))f[find(b)]=find(a);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
for(int i=0;i<1005;i++)f[i]=i;
cin>>n>>m;
for(int i=0;i<m;i++){
int a,b,c;
cin>>a>>b>>c;
if(a>b)swap(a,b);
e[i]=(edge){a,b,-1*c};
}
sort(e,e+m,cmp);
for(int i=0;i<m&&add<=n;i++){
int u=e[i].u,v=e[i].v,w=e[i].w;
if(find(u)!=find(v)){
link(u,v);
ans+=w;
add++;
}
}
if(add!=n)cout<<-1;
else cout<<-1*ans;
return 0;
}