问题描述
有一张 n n n 个顶点、 m m m 条边的无向图,且是连通图,求最小生成树。
Prim算法简析
P
r
i
m
Prim
Prim 算法是一种求最小生成树的算法。
设该图为
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)。
G
−
G
A
G - G_A
G−GA 为
G
B
=
(
V
B
,
E
B
)
G_B = (V_B, E_B)
GB=(VB,EB), 其中
V
B
=
V
−
V
A
V_B = V - V_A
VB=V−VA。
为了这棵树
G
A
G_A
GA 继续生长为最小生成树
G
T
G_T
GT,依据贪心策略,我们要挑选权值最小的边加入
G
A
G_A
GA。也就是说,从连接
G
A
G_A
GA 和
G
B
G_B
GB 的边中挑选权值最小的边。
代码实现
#include <bits/stdc++.h>
using namespace std;
#define MAX 10 // 最大定点数
#define INF 1e8
// 定义边
typedef struct
{
int to, worth;
} edge;
typedef pair<int, int> P; // first -- 顶点编号; second -- 该顶点到树的最短距离
vector<edge> G[MAX]; // 邻接链表
int d[MAX]; // i 到树的最小距离
bool vis[MAX]; // i 是否在树中
// 最小堆
struct cmp
{
bool operator()(const P &a, const P &b)
{
return a.second > b.second;
}
};
int prim(void)
{
// 初始化
fill(begin(d), end(d), INF);
d[1] = 0; // 以 1 为根节点
priority_queue<P, vector<P>, cmp> Q;
Q.push(P(1, 0));
int ans = 0; // 最小生成树
while (!Q.empty())
{
P p = Q.top(); // p -- 连接 G_A 和 G_B 的最短边
Q.pop();
int u = p.first;
if (vis[u]) // u 是否在树中
continue; // 若在,则跳过该点
ans += d[u]; // 若不在,加入树中
vis[u] = true; // u 已加入树
// 遍历 u 为起点的每一条边
for (int i = 0; i < G[u].size(); i++)
{
edge e = G[u][i];
if (!vis[e.to] && d[e.to] > e.worth) // 寻找未加入树且最短的边
{
d[e.to] = e.worth; // 更新 e.to 到 G_A 的最短路径
Q.push(P(e.to, e.worth));
}
}
}
return ans;
}
注:
- 1、这里我们选择 1 1 1 为根节点。其实,可以选择任意顶点为根节点。因为 G G G 是连通图,所以最终的最小生成树一定覆盖所有的顶点 G . V G.V G.V。
- 2、该算法的关键是如何找到连接
G
A
G_A
GA 和
G
B
G_B
GB 的最短边,这里,我们采用了最小优先队列(以边的权值为排序依据)。对于
G
A
G_A
GA 的一个顶点
u
u
u,遍历从它的所有边,选择符合要求的边加入
Q
Q
Q。这样,取出的边就是最短边了。
我们来分析一下这里的要求if (!vis[e.to] && d[e.to] > e.worth)
。首先,要明确一点,虽然我们在挑选 u u u 的边,但其实,也在挑选边的终点 v v v。因此,顶点 v v v 必须不在 G A G_A GA 中,这就是条件一。同时, e ( u , v ) e(u, v) e(u,v) 可能有重边,不止一条。所以,我们要挑选重边中 e ( u , v ) . w e(u, v).w e(u,v).w 最小的,这就是条件二。
仔细看这个 i f if if 语句,我们会发现,队列里可能依然有 e ( u , v ) e(u, v) e(u,v) 的重边,但肯定有 e ( u , v ) e(u, v) e(u,v) 的最短边。这里,我们这样处理重边:首先,最小优先队列保证了取出来的一定是最短边;其次,if (vis[u])
这个判断语句跳过了剩下的重边(因为重边都有一个公共顶点 v v v,然而,由于最小优先队列,它已经加入 G A G_A GA)。 - 3、我们可以发现,最小生成树算法
P
r
i
m
Prim
Prim 与最短路径算法
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra 很相似。其中,
d[]
的意义并不一样。Prim
: d [ u ] = d[u]= d[u]= 顶点 u u u 到 G A G_A GA 的最短路径。Dijkstra
: d [ u ] = d[u]= d[u]= 顶点 u u u 到起点 s s s 的最短路径。
完