跳转至

Kruskal 算法

简介

Kruskal 算法是一种常见、好写的最小生成树算法,发明者为 Kruskal。使用了贪心、排序等算法和并查集。

算法思想

我们可以维护一个森林,然后每一次取出最优的边,判断这两个结点在不在同一个树当中,如果在,那么就说明他们已经联通了,就抛弃掉这条边;否则,就将这条边所在的两棵树合并,并累加答案。抽象一点,其实就是维护一堆集合,而这个任务可以交给并查集轻松地处理。

如果排序时间复杂度为 \(\mathcal O(m\log_2 m)\),并查集时间复杂度为 \(\mathcal O(m\alpha(n))\),那么该算法的时间复杂度为 \(\mathcal O(m\log_2 m)\)。对于值域范围比较小的图,我们可以使用基数排序或者计数排序,那么时间复杂度就能接近 \(\mathcal O(m\alpha(n))\)

代码
int F(int x) {
  return f[x] == x ? x : f[x] = F(f[x]);
}

void U(int u, int v) {
  u = F(u), v = F(v);
  u != v && (f[u] = v);
}

int kruskal() {
  sort(e + 1, e + m + 1, [](const Edge &a, const Edge &b) {
    return a.w < b.w;
  });
  int ans = 0;
  for (int i = 1; i <= m; i++) {
    if (F(e[i].u) == F(e[i].v)) {
      continue;
    }
    U(e[i].u, e[i].v);
    ans += e[i].w;
  }
  return ans;
}

最大生成树也是同理,只需要将排序当中的小于号改成大于号就行了。

注意

Kruskal 算法对边 \(m\) 的大小较为依赖,因此在完全图(\(m\approx n^2\) )当中,Kruskal 算法可能会在排序的时候被卡,但是 Prim 算法的 \(\mathcal O(n^2~)\) 就可以通过。如果你不想出现这种尴尬的情况,就可以学习一下 Prim 算法。

评论