25.最小生成树

一、问题介绍

生成树被定义为:一个连通无向图的生成子图,同时要求是树。也即在图的边集中选择 n − 1 n-1 n1 条,将所有顶点连通。

我们将最小生成树(Minimum Spanning Tree,MST)定义为:无向连通图的为边权和最小的生成树。

只有连通图才有生成树,而对于非连通图,只存在生成森林。

现在广泛使用的最小生成树算法主要有两种,分别为 Kruskal 算法和 Prim 算法,下面具体介绍一下每个算法。

二、Kruskal算法

1.简介

Kruskal 算法是一种常见并且好写的最小生成树算法,由 Kruskal 发明。该算法的基本思想是从小到大加入边,是个贪心算法,一般需要借助并查集这一数据结构来实现。

2.算法流程

  • 将所有的边按边权从小到大排序,这里使用了贪心的思想
  • 维护一个森林,按顺序取出边,查询边的两个结点是否在同一棵树中
    • 如果在,则不进行任何操作。
    • 如果不在,则连接两棵树,将这条边计入最小生成树。
  • 查询两点是否在同一棵树和连接两树使用并查集维护。
  • 时间复杂度 O ( m log ⁡ m ) O(m\log m) O(mlogm)

3.贪心证明

思路很简单,为了造出一棵最小生成树,我们从最小边权的边开始,按边权从小到大依次加入,如果某次加边产生了环,就扔掉这条边,直到加入了 n − 1 n-1 n1 条边,即形成了一棵树。

证明:使用归纳法,证明任何时候 Kruskal 算法选择的边集都被某棵 MST 所包含。

  • 基础:对于算法刚开始时,没有任何一条边,显然成立(最小生成树存在)。

  • 归纳:假设某时刻成立,当前边集为 F,令 T 为这棵 MST,考虑下一条加入的边 e。

    • 如果 e 属于 T,那么成立。
    • 否则,T+e 一定存在一个环,考虑这个环上不属于 F 的另一条边 f(一定只有一条)。

首先,f 的权值一定不会比 e 小,不然 f 会在 e 之前被选取。

然后,f 的权值一定不会比 e 大,不然 T+e-f 就是一棵比 T 还优的生成树了。

因此,f 和 e 的权值相等,T+e-f 也是一棵最小生成树,且包含了 F。

4.模板代码

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;
}

三、Prim算法

1.简介

Prim 算法是另一种常见并且好写的最小生成树算法。该算法的基本思想是从一个结点开始,不断加点,这和以加边的方式进行的 Kruskal 算法有所不同。在算法的流程上与 Dijkstra有更多的相似之处。

2.算法流程

其实跟 Dijkstra 算法一样,每次找到距离最小的一个点,以及用新的边更新其他结点的距离,可以暴力找也可以用堆维护。

  • 从任意一个结点开始,将结点分成两类:已加入的,未加入的。
  • 每次从未加入的结点中,找一个与已加入的结点之间边权最小值最小的结点(dijkstra 算法这里是找的离源点最近的点)。
  • 然后将这个结点加入,并连上那条边权最小的边.
  • 重复 n-1 次即可。

堆优化的方式类似 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)

3.证明

证明:在每一步,都存在一棵最小生成树包含已选边集。

  • 基础:只有一个结点的时候,显然成立。
  • 归纳:如果某一步成立,当前边集为 F,属于 T 这棵 MST,接下来要加入边 e。
    • 如果 e 属于 T,那么显然成立。
    • 否则,考虑 T+e 中形成的环上,另一条可以加入当前边集的边 f。

首先,f 的权值一定不小于 e 的权值,否则就会选择 f 而不是 e 了。

然后,f 的权值一定不大于 e 的权值,否则 T+e-f 就是一棵更小的生成树了。

因此,f 和 e 的权值相等,T+e-f 也是一棵最小生成树,且包含了 F。

4.模板代码

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]);
	}
}

四、作业

P3366 【模板】最小生成树

1.Kruskal算法

P1111 修复公路

P2820 局域网

P1546 [USACO3.1]最短网络 Agri-Net

P1195 口袋的天空

P2330 [SCOI2005]繁忙的都市

2.Prim算法

P2504 [HAOI2006] 聪明的猴子

P1194 买礼物

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值