生成树被定义为:一个连通无向图的生成子图,同时要求是树。也即在图的边集中选择 n − 1 n-1 n−1 条,将所有顶点连通。
我们将最小生成树(Minimum Spanning Tree,MST)定义为:无向连通图的为边权和最小的生成树。
只有连通图才有生成树,而对于非连通图,只存在生成森林。
现在广泛使用的最小生成树算法主要有两种,分别为 Kruskal 算法和 Prim 算法,下面具体介绍一下每个算法。
Kruskal 算法是一种常见并且好写的最小生成树算法,由 Kruskal 发明。该算法的基本思想是从小到大加入边,是个贪心算法,一般需要借助并查集这一数据结构来实现。
思路很简单,为了造出一棵最小生成树,我们从最小边权的边开始,按边权从小到大依次加入,如果某次加边产生了环,就扔掉这条边,直到加入了 n − 1 n-1 n−1 条边,即形成了一棵树。
证明:使用归纳法,证明任何时候 Kruskal 算法选择的边集都被某棵 MST 所包含。
基础:对于算法刚开始时,没有任何一条边,显然成立(最小生成树存在)。
归纳:假设某时刻成立,当前边集为 F,令 T 为这棵 MST,考虑下一条加入的边 e。
首先,f 的权值一定不会比 e 小,不然 f 会在 e 之前被选取。
然后,f 的权值一定不会比 e 大,不然 T+e-f 就是一棵比 T 还优的生成树了。
因此,f 和 e 的权值相等,T+e-f 也是一棵最小生成树,且包含了 F。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const ll maxn=5010;
const ll maxm=200010;
struct node
{
ll u;
ll v;
ll w;
}e[maxm];
bool cmp (node a,node b)
{
return a.w<b.w;
}
ll fa[maxn];
ll get(ll x)
{
if(x==fa[x])
return x;
return fa[x]=get(fa[x]);
}
ll kruskal(ll n,ll m)
{
ll sum=0;
for(ll i=1;i<=n;i++)
fa[i]=i;
sort(e,e+m,cmp);
for(ll i=0;i<m;i++)
{
ll fu=get(e[i].u);
ll fv=get(e[i].v);
if(fu!=fv)
{
fa[fv]=fu;
sum+=e[i].w;
}
}
return sum;
}
int main()
{
ll n,m;
cin>>n>>m;
for(ll i=0;i<m;i++)
cin>>e[i].u>>e[i].v>>e[i].w;
cout<<kruskal(n,m)<<endl;
return 0;
}
Prim 算法是另一种常见并且好写的最小生成树算法。该算法的基本思想是从一个结点开始,不断加点,这和以加边的方式进行的 Kruskal 算法有所不同。在算法的流程上与 Dijkstra有更多的相似之处。
其实跟 Dijkstra 算法一样,每次找到距离最小的一个点,以及用新的边更新其他结点的距离,可以暴力找也可以用堆维护。
堆优化的方式类似 Dijkstra 的堆优化,但如果使用二叉堆等不支持 O ( 1 ) O(1) O(1) decrease-key 的堆,复杂度就不优于 Kruskal,常数也比 Kruskal 大。所以,一般情况下都使用 Kruskal 算法。而在稠密图尤其是完全图上,暴力 Prim 的复杂度比 Kruskal 优,但 不一定 实际跑得更快。
暴力: O ( n 2 + m ) O(n^2+m) O(n2+m);二叉堆: O ( ( n + m ) log n ) O((n+m)\log n) O((n+m)logn);Fib 堆: O ( n log n + m ) O(n \log n + m) O(nlogn+m)
证明:在每一步,都存在一棵最小生成树包含已选边集。
首先,f 的权值一定不小于 e 的权值,否则就会选择 f 而不是 e 了。
然后,f 的权值一定不大于 e 的权值,否则 T+e-f 就是一棵更小的生成树了。
因此,f 和 e 的权值相等,T+e-f 也是一棵最小生成树,且包含了 F。
#include
#include
#include
#include
using namespace std;
typedef int ll;
const ll maxn=5010;
ll mp[maxn][maxn],d[maxn],n,m,ans,cnt=0;
bool vis[maxn];
void prim()
{
memset(d,0x3f,sizeof(d));
memset(vis,false,sizeof(vis));
d[1]=0;
for(ll i=1;i<n;i++)
{
ll x=0;
for(ll j=1;j<=n;j++)
if(!vis[j] && (x==0 || d[j]<d[x]))
x=j;
if(d[x]!=d[0])
cnt++;
vis[x]=true;
for(ll y=1;y<=n;y++)
if(!vis[y])
d[y]=min(d[y],mp[x][y]);
}
}
int main()
{
memset(mp,0x3f,sizeof(mp));
cin>>n>>m;
for(ll i=0;i<m;i++)
{
ll x,y,z;
cin>>x>>y>>z;
mp[x][y]=mp[y][x]=min(mp[x][y],z);
}
prim();
for(ll i=2;i<=n;i++)
ans+=d[i];
if(cnt==n-1)
cout<<ans<<endl;
else
cout<<"orz"<<endl;
return 0;
}
P3366 【模板】最小生成树
P1111 修复公路
P2820 局域网
P1546 [USACO3.1]最短网络 Agri-Net
P1195 口袋的天空
P2330 [SCOI2005]繁忙的都市
P2504 [HAOI2006] 聪明的猴子
P1194 买礼物