最小生成树
最小生成树
我们定义无向连通图的最小生成树(MST)为边权和最小的生成树。
【 最小生成树可以用来解决用最小的“代价”用N-1条边连接N个点的问题。】 例题:
[USACO3.1]最短网络 Agri-Net
John 被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。FJ 已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过 105。
第一行农场的个数 N(3 ≤ N ≤ 100)。
接下来是一个 N X N 的矩阵,表示每个农场之间的距离。理论上,他们是 N 行,每行由 N 个用空格分隔的数组成,实际上,由于每行 80 个字符的限制,因此,某些行会紧接着另一些行。当然,对角线将会是 0,因为不会有线路从第 i 个农场到它本身。
只有一个输出,其中包含连接到每个农场的光纤的最小长度。
Input
4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0
Output
28
我们来介绍两种求解最小生成树的算法,Kruskal算法 和 Prim算法 。
Kruskal
前置知识:并查集、贪心
1.【原理】
我们先认为图上的每一个点都是单个的点,(并查集思路,即fa[i] = i;
然后将每个点通过给出的边连接为一颗树,通过贪心的思想,要先将最小的边加入到M S T中(排序,即sort(e + 1, e + 1 + m, cmp);
,对于加边操作,我们使用并查集来维护,fa[fat(e[i].u)] = fat(e[i].v);
所以利用贪心的思想,将权值按照从小到大排序,然后将小边加入到M S T中,为了防止形成环,要在选边的过程中利用并查集进行判断是否已经连边,就是Kruskal算法的大体过程。
2.【图示】咕咕咕~~~
3.【特别地】Kruskal的时间复杂度为 \(O(m\log n)\),因此有利于稀疏图。
4.【代码实现】
void Kruskal() {
for (int i = 1; i <= n; ++ i) fa[i] = i; //并查集 预处理
sort(e + 1, e + 1 + m, cmp); //按照 边权大小 进行排序
for (int i = 1; i <= m; ++ i) {
int fat_u = fat(e[i].u);
int fat_v = fat(e[i].v);
if (fat_u == fat_v) continue; //判断是否 已经连边
fa[fat_u] = fat_v; //加边操作
ans += e[i].w; //记录边权和
//………………
tot ++ ;
if (tot == n - 1) break; //树的边数为点数-1 因此跳出
}
}
Prim
1.【原理】
与Dijkstra和SPFA的“蓝白点”的思路是相似的,白点代表已经进入最小生成树的点,蓝点代表未进入最小生成树的点。
2.【图示】咕咕咕~~~
3.【特别地】有利于稠密图。
4.【代码实现】
void Prim() {
for (int i = 1; i <= n; ++ i) {
int k = 0
for (int j = 1; j <= n; ++ j) //找一个与白点相连的权值最小的蓝点k
if(!vis[j] && mp[j] < mp[k])
k = j;
vis[k] = true; //蓝点k加入生成树,标记为白点
for (int j = 1; j <= n; ++ j)
if(!vis[j] && sm[k][j] < dis[j])
dis[j] = sm[k][j];
}
for (int i = 1; i <= n; ++ i) //累加权值
ans += dis[i];
}
void Prim() {
priority_queue <PAIR> q;
int ans = 0;
dis[1] = 0;
q.push(make_pair(dis[1],1));
while (!q.empty()){
int u = q.top().second, d = q.top().first;
q.pop();
if (vis[u]) continue;
vis[u] = true;
ans += d;
for (int i = h[u]; i; i = e[i].nxt) {
int v = sm[i].v;
if (dis[v] > sm[i].w) {
dis[v] = sm[i].w;
q.push(make_pair(dis[v],v));
}
}
}
}