10/21 Kruskal算法

Background:

    在无向图中,连通并且不含圈的图称为树(Tree)。给定无向图G = (V, E), 连接G中所有点,且边集是E的子集的树的生成树称为G的(Spanning Tree),而权值最小的生成树称为最小生成树(Minimal Spanning Tree, MST)。Kruscal算法就是构造最小生成树的一种算法。


Kruskal Algorithm:

Steps: 

1. 给所有边按照从小到大的顺序排列。

2. 情况1: 如果u, v在同一个连通分量中,那么加入(u, v)之后会形成环,因此不能选择。

    情况2: 如果u, v在不同的连通分量,那么加入(u, v)一定是最优的。用反证法:如果不加入这条边能得到一个最优解T,则T + (u, v)一定有且只有一个环,而且环中至少有一条边(u', v')的权值大于或者等于(u, v)的权值。删除该边之后,得到的新树T' = T + (u, v) - (u', v')不会比T更差。因此,加入(u, v)不会比不加入差。


伪代码:

将所有边排序,记第i小的边为e[i] (1 <= i < m)

初始化MST为空

初始化连通分量,让每一个点都自成一个连通分量

for (int i = 0; i < m; i++) 

    if (e[i].u 和 e[i].v 不在同一个连通分量) {

      把边e[i]加入MST 

      合并e[i].u和e[i].v所在的连通分量

     }

核心:如何快速检查e[i].u和e[i].v是否在同一个连通分量中。


Solution: Union-Find Set:

可以把每个连通分量看成一个集合,该集合包含了连通分量中的所有点。这些点两两相通,而具体的连通方式无关紧要,就好比集合中的元素没有先后顺序之分,只有“属于”和不“属于”的区别。

Union-Find Set的精妙之处在于用树表示集合。规定每棵树的根结点是这棵树所对应的集合的代表元(representative)。

int find(int x) { if (p[x] = x) return x; return find(p[x]); } 

极端情况:这棵树是一条长长的链,假设链的最后一个结点是x,则每次执行find(x)都会遍历整个树,效率十分低下。

Solution: 改进find方法,只要把遍历过的结点都改成树根的字结点,下次查询的时候就会快很多。


Code: 

int cmp(const int i, const j) { return w[i] < w[j]; }
int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
int Kruskal() {
	int ans = 0;
	for (int i = 0; i < n; i++) p[i] = i;
	for (int i = 0; i < m; i++) r[i] = i;
	sort(r, r + m, cmp);
	for (int i = 0; i < m; i++) {
		//r[i] 排序后第i小的边
		int e = r[i]; int x = find(u[e]); int y = find(v[e]);
		if (x != y) { ans += w[e]; p[x] = y;}
	}
	return ans;
}

Note: 注意不能写成p[u[e]] = p[v[e]],因为u[e]和v[e]不一定是树根,所以不能这两棵树的连通。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值