Prim算法
P r i m Prim Prim算法和 D i j k s t r a Dijkstra Dijkstra最短路算法十分相似。我们从 1 1 1号节点出发,不断地向生成树里加最小权值边。
假设我们已有一颗树 T T T,满足 T T T为一颗最小生成树的子图。令 X X X为 T T T的点集, e e e为连接 X X X和 V ∖ X V\setminus X V∖X的最小权值边,我们要证明存在一棵生成树包含了 T T T和 e e e。
同样,假设包含 T T T的最小生成树不包含 e e e,那么将 e e e加到这颗树里之后一定会形成一个环,且此时除 e e e以外还有另一条边 f f f连接着 X X X和 V ∖ X V\setminus X V∖X。根据定义 w f ≥ w e w_f \ge w_e wf≥we,所以我们将 f f f从树中删去,然后加上 e e e就可以得到一颗新的生成树且权值和不超过原来的生成树。
所以我们不断地加入最小权值边,直到 X = V X=V X=V,由于存在一颗生成树满足 T T T是它的子图,于是就求得了一颗最小生成树 T T T。
时间复杂度: O ( ∣ E ∣ log ∣ V ∣ ) O(|E|\log|V|) O(∣E∣log∣V∣)。
空间复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(∣V∣+∣E∣)。
代码
int n, m;
vector<Edge> e[N];
LL d[N];
bool used[N];
LL Prim(){ // 默认图联通
rep1(i, n + 1) d[i] = inf;
d[1] = 0;
priority_queue<pair<LL, int> > pq;
pq.push(MP(-d[1], 1));
used[1] = true;
LL ret = 0;
while (!pq.empty()){
int cur = pq.top().second;
LL curdis = -pq.top().first;
pq.pop();
if (curdis > d[cur]) continue;
ret += curdis;
used[cur] = true;
for (Edge u : e[cur]) if (u.len < d[u.to] && !used[u.to]){
d[u.to] = u.len;
pq.push(MP(-d[u.to], u.to));
}
}
return ret;
}
Kruskal算法
K r u s k a l Kruskal Kruskal是一个非常简单实用的最小生成树算法,代码难度比 P r i m Prim Prim低很多,而且跑得和 P r i m Prim Prim一样快,一般情况下还有推荐使用 K r u s k a l Kruskal Kruskal。
K r u s k a l Kruskal Kruskal算法的核心思想就是将所有边按权值从小到大排序,不断地判断当前边加入后会不会使生成树出现环,如果不会形成环,那么就将当前边加入生成树。判环操作其实就等价于判断当前边的两个端点是否已属于同一集合,这可以用并查集很方便地维护。
时间复杂度: O ( ∣ E ∣ log ∣ V ∣ ) O(|E|\log|V|) O(∣E∣log∣V∣)。
空间复杂度: O ( ∣ E ∣ ) O(|E|) O(∣E∣)。
代码
int n, m;
vector<Edge> edges;
LL Kruskal(){ // 默认图联通
sort(edges.begin(), edges.end(), [&](Edge x, Edge y){ return x.len < y.len; });
dsu.init();
int cnt = 0;
LL ret = 0;
rep(i, edges.size()){
if (cnt == n - 1) break;
if (dsu.find(edges[i].from) == dsu.find(edges[i].to)) continue;
cnt ++;
ret += edges[i].len;
dsu.merge(edges[i].from, edges[i].to);
}
return ret;
}