今天讲述一下基于贪心思想的最小生成树Kruskal算法
先来看一下什么是生成树——
定义:如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树(SpanningTree)【摘自百度百科】
画图来理解一下
这是一个普普通通的无向图——
它的一个生成树
它的另一棵生成树
………………
由此我们可以发现,一个无向图有若干棵生成树
再来一句定义—— 设定点数 n, 边数m, 则生成树一共有 n - 1条边,所以,一共要删去 m - n + 1 条边
接下来,如果我们把这个无向图带上权值
那么,就自然引出——最小生成树中, “ 最小 ” 的概念了
那就是最终生成树的边权和最小
所以上面这个无向带权图的最小生成树就是——
然而怎么求一个无向图的最小生成树呢?常见有普里姆算法(Prim)和克鲁斯卡尔(Kruskal)算法
今天,我们就先讲讲相对简单的Kruskal算法
现在,先把所有边都删去——
自然,我们也不能不管边啊,是吧——
我们用一个数组来存储边的信息——
因为是无向边,所以哪里到哪里不重要,重要的是一个定点组<V1, V2>, 和它的边权
因为要求 “ 最小 ” ,我们不妨把它们按照边权 w 的大小来排个序
然后遍历这个存边的信息的数组,并把它加到我们的无向图里
第一次:
选取 C and D, 2
第二次:
选取 A and D, 3
第三次:
选取A and B, 5
诶?此时,还记得我们说过——一共要有 n - 1条边吗?这里,我们加入的边的数量达到了3 = 4 - 1,所以退出遍历存边的信息的数组,得到了我们的最小生成树
大致的思路是如此,但是还有许多小细节没有说——
并查集
假设有一条B and C 的边,但是B,C联通了,我们应该就不用这条边了
有图举例子:
比如此时此刻,我们存边的信息的数组是——
(就是多了一条C and E)
那么此时此刻,我们遍历到了 B and C, 7, 这里,但是B C 已经联通了啊,要是再加进去,那么我们的答案就一定不是 “ 最小生成树 ” 了啊,甚至都不是生成树
综上,我们在加边的时候,还要判断
if 联通(A, B) == True: #--- 表示A, B 连通
Add(A, B, w) #--- w 表示边权
count += 1 #--- count 统计添加的边的数量
问题来了——我们如何判断定点<V1, V2> 是否联通呢?——
这里就引入了 并查集 的概念
欲知并查集为何方神圣,推荐这个Blog,(请大家期待一下后面我的并查集文章吧!)
URL:【算法与数据结构】—— 并查集
好,有了并查集,一切事情都好办了,我们再总结一下实现Kruskal的流程:
for i in range(m): # 读入 m 条边
read(from, to, w);
edges.append(from, to, w); # 存边的信息
sort(edges) # 排序
for i in edges:
if join(i.from, i.to): # 如果联通
Add(i.from, i.to)
count += 1
w += i.w # 统计最小生成树的权值和
if count == n - 1: # 退出条件
break
有了以上伪代码,我们就开始真正实现Kruskal算法了
#include <stdio.h>
#include <algorithm>
class edge
{
public:
edge(int ofrom = 0, int oto = 0, int ow = 0): from(ofrom), to(oto), w(ow)
{
};
int from, w, to;
};
edge gra[200005]; // 按顺序存边
int pre[5005];
int n, m, u, v, w, count = 0, ans = 0;
bool compare(const edge x, const edge y)
{
return x.w < y.w;
}
int find(int root) // 并查集操作之
{
if(pre[root] == root)
return root;
return pre[root] = find(pre[root]);
}
bool join(int root1, int root2) // 并查集操作之并集
{
int x1 = find(root1);
int x2 = find(root2);
if(x1 != x2)
{
pre[x1] = x2;
return true;
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++) // 读入 m 条边
{
scanf("%d%d%d", &u, &v, &w);
gra[i] = edge(u, v, w);
}
for(int i = 1; i <= n; i++) // 初始化并查集
pre[i] = i;
std::sort(gra + 1, gra + 1 + m, compare); // 排序
for(int i = 1; i <= m; i++)
{
if(join(gra[i].to, gra[i].from))
{
ans += gra[i].w;
count += 1;
}
if(count == n - 1)
break;
}
if(count != n - 1) // 无法生成最小生成树
puts("Can't Create!");
else
printf("%d\n", ans);
return 0;
}
好了,讲到这里,就结束了。
若是对这些有兴趣的,请一定一定关注我,一起多多学习
我是Michael_cmr,蒟蒻一枚。如果有说错的地方,各位大神千千万万要指出来。
谢谢大家!欢迎各位大神指点!
(转载请标注出处与博主姓名)
(QQ:2437844684)
(欢迎各位大神评论)
——————都看到这里了,不点个赞是不是也觉得不好意思呀~