问题描述
有一张 n n n 个顶点、 m m m 条边的无向图,且是连通图,求最小生成树。
算法简析
K
r
u
s
k
a
l
Kruskal
Kruskal 是一种求最小生成树的算法。
设该图为
G
=
(
V
,
E
)
G = (V, E)
G=(V,E)。最小生成树即所求为
G
T
=
(
V
T
,
E
T
)
G_T = (V_T, E_T)
GT=(VT,ET),因为图是连通的,所以最小生成树会覆盖所有的顶点,即
V
=
=
V
T
V == V_T
V==VT。
G
T
G_T
GT 的真子图
G
A
=
(
V
A
,
E
A
)
G_A = (V_A, E_A)
GA=(VA,EA),
V
A
=
=
V
V_A == V
VA==V,构成森林(注意,这里与
P
r
i
m
Prim
Prim 并不一样。
G
A
G_A
GA 初始状态就覆盖所有顶点,每个顶点各自为一棵树)。
因为
G
A
G_A
GA 已经覆盖了所有顶点,所以
G
−
G
A
G - G_A
G−GA 只剩下
G
G
G 中所有的边
E
E
E。为了让
G
A
G_A
GA 从森林变成一棵树,我们需要从
E
E
E 中挑选边加入
G
A
G_A
GA。例如,我们选择
e
(
u
,
v
)
e(u, v)
e(u,v) 加入
G
A
G_A
GA,本来独立的
u
u
u 和
v
v
v 被连接起来,相当于
u
u
u 和
v
v
v 各自所在的两棵树要合并成一棵树。
为了得到最小生成树,我们采用贪心策略,每次都从
E
E
E 中挑选最短的边
e
(
u
,
v
)
e(u, v)
e(u,v),如果
u
u
u 和
v
v
v 还不在一棵树上,就将该边加入
G
A
G_A
GA,同时合并两棵树。
为了能够高效地合并树,我们采用并查集。
代码
typedef struct
{
int from, to, worth;
} edge;
vector<edge> E;
// Kruskal
bool cmp(const edge &a, const edge &b)
{
return a.worth < b.worth;
}
int kruskal(void)
{
int ret = 0;
init();
sort(E.begin(), E.end(), cmp); // 升序
for (int i = 0; i < E.size(); i++)
{
edge e = E[i];
if (!same(e.from, e.to)) // 判断是否在一棵树中
{
ret += e.worth;
unite(e.from, e.to); // 合并树
}
}
return ret;
}
完