caioj1090最小生成树之kruskal算法模板元问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了caioj1090最小生成树之kruskal算法模板元问题相关的知识,希望对你有一定的参考价值。
首先总结一下概念:
什么是最小生成树呢?
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
个人认为kruskal算法还是比较优秀的。
所以此篇重点讨论kruskal算法。
1.概览
Kruskal算法是一种用来寻找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪婪算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在相同权值的边时也有效。
图例描述:
首先第一步,我们有一张图Graph,有若干点和边
将所有的边的长度排序,用排序的结果作为我们选择边的依据。这里再次体现了贪心算法的思想。资源排序,对局部最优的资源进行选择,排序完成后,我们率先选择了边AD。这样我们的图就变成了右图
在剩下的变中寻找。我们找到了CE。这里边的权重也是5
依次类推我们找到了6,7,7,即DF,AB,BE。
下面继续选择, BC或者EF尽管现在长度为8的边是最小的未选择的边。但是现在他们已经连通了(对于BC可以通过CE,EB来连接,类似的EF可以通过EB,BA,AD,DF来接连)。所以不需要选择他们。类似的BD也已经连通了(这里上图的连通线用红色表示了)。
3.简单证明Kruskal算法
对图的顶点数n做归纳,证明Kruskal算法对任意n阶图适用。
归纳基础:
n=1,显然能够找到最小生成树。
归纳过程:
假设Kruskal算法对n≤k阶图适用,那么,在k+1阶图G中,我们把最短边的两个端点a和b做一个合并操作,即把u与v合为一个点v‘,把原来接在u和v的边都接到v‘上去,这样就能够得到一个k阶图G‘(u,v的合并是k+1少一条边),G‘最小生成树T‘可以用Kruskal算法得到。
我们证明T‘+{<u,v>}是G的最小生成树。
用反证法,如果T‘+{<u,v>}不是最小生成树,最小生成树是T,即W(T)<W(T‘+{<u,v>})。显然T应该包含<u,v>,否则,可以用<u,v>加入到T中,形成一个环,删除环上原有的任意一条边,形成一棵更小权值的生成树。而T-{<u,v>},是G‘的生成树。所以W(T-{<u,v>})<=W(T‘),也就是W(T)<=W(T‘)+W(<u,v>)=W(T‘+{<u,v>}),产生了矛盾。于是假设不成立,T‘+{<u,v>}是G的最小生成树,Kruskal算法对k+1阶图也适用。
由数学归纳法,Kruskal算法得证。
首先提示一下,毕竟是棵树,我们决不能让它形成环,所以要引入并查集。当发现一条路径,判断如果两个点的祖先已经一样了,说明这两个点之前已被纳入最小生成的树的范畴,纳入则成环,故此时不将其并入并查集(若并查集概念不清楚的可以访问https://baike.baidu.com/item/%E5%B9%B6%E6%9F%A5%E9%9B%86/9388442?fr=aladdin,这个很容易弄清楚)。
还是附一下题:
1090: [视频]最小生成树(模版 kruskal算法 元问题by scy)
时间限制: 1 Sec 内存限制: 128 MB题目描述
【题目描述】 一个有n个点的连通无向图,有m条无向边,每条边有一个长度c, 如果连接所有点,只需要从m条无向边中选n-1条,为什么? 现在要求这n-1条边的长度和最小。
以上就是最小生成树的概念。
【输入格式】 第一行输入 n和 m (1<=n<=1000,n-1<=m<=50 0000) 下来 m 行,每行三个数 x,y,c,表示点 x 和 点 y 有一条距离为 c 的无向边。0<c<=20 【输出格式】 一行一个整数,即连接n个点的最小长度和。 【样例1输入】 5 5 1 2 1 2 3 1 3 4 2 4 5 1 5 1 1 【样例1输出】 4 【样例2输入】 7 9 1 6 5 5 1 6 7 4 1 6 7 8 5 3 2 4 5 3 4 2 4 2 3 5 3 1 7 【样例2输出】 21
接下来我们用代码说话:
#include<cstdio>
#include<algorithm>
using namespace std;
struct edge{//结构体来存边的信息
int a,b,c;
// bool operator < (const edge &x) const 重载<运算符,当然为了结构体的比较你在外面写函数也行
// { return c<x.c; }
}e[500000];//边的信息比较多,这里不要吃亏!开大一点
int fa[1005];//并查集
int n,m;
bool cmp(const edge &x1,const edge &x2)
{
return x1.c<x2.c;
}
int find(int x)//找x的老祖先
{
if (fa[x]!=x) return fa[x]=find(fa[x]);//都是套路,递归压缩路径防超时
return fa[x];
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) fa[i]=i;//并查集的初始化
for (int i=1;i<=m;i++) scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c);
sort(e+1,e+m+1,cmp);
int t=0;//计边数
int ans=0;//计答案
for (int i=1;i<=m;i++)
{
int k1=find(e[i].a);
int k2=find(e[i].b);
if (k1!=k2)//太好了!说明可以合并!
{
fa[k2]=k1;
ans+=e[i].c;
t++;
if (t==n-1) break;
}
}
printf("%d\\n",ans);
return 0;
}
以上是关于caioj1090最小生成树之kruskal算法模板元问题的主要内容,如果未能解决你的问题,请参考以下文章