题目链接
最小生成树一般是利用贪心的克鲁斯卡尔算法Prim不会写,克鲁斯卡尔是一个离线算法,首先将边读入,按边值排序,再从小往大加边,若两个端点在一起了,则不加,否则加,直至加了n-1条边,若不行,则无最小生成树。
这是贪心的来想,现在让我们来证明一下
证明
1.令Kruskal算法得到的树为K,有一棵最小生成树T,假设他们不同。
2.找到边权最小的在K但不在T中的边e。
3.把e加入T中,形成一个环,删掉这个环中一条不在K中的边e′,得到新生成树T′。
4.若不存在e′则K存在环,矛盾。
5.若w(e′)>w(e),则T′权值和小于T,矛盾。
6.若w(e′)<w(e),则Kruskal执行时先考虑了e′,由于成环没加入e′,因此在e′之前加入。的边权值均≤w(e′)<w(e).由e的定义,K中边权小于w(e)的边均在T中,说明T中边与e′会成环,矛盾。
7.w(e′) =w(e),在T中用e换掉e′。
8.有限步后可把T变为K且权值不变,因此K就是最小生成树。
证毕
下面画图解释一下
这是读入时的状态
将边都去掉
下面开始连边
2和4根据并查集可知已经在一个集合中,所以不用连这条边。
时间:500ms。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int f[5001];
struct node
{
int x,y,z;
}al[200001];
bool cmp(node a,node b)
{
return a.z<b.z;
}
int find(int a)
{
if(f[a]==a)
{
return a;
}
return f[a]=find(f[a]);
}
int main()
{
int ans,n,m,i,j,k;
cin>>n>>m;
for(i=1;i<=5000;i++)
{
f[i]=i;
}
for(i=1;i<=m;i++)
{
cin>>al[i].x>>al[i].y>>al[i].z;
}
sort(al+1,al+1+m,cmp);
k=n;
for(i=1;i<=m;i++)
{
if(find(al[i].x)==find(al[i].y))
{
continue;
}
f[find(al[i].x)]=find(al[i].y);
k--;
ans+=al[i].z;
if(k==1)
{
break;
}
}
cout<<ans;
return 0;
}
说完了Kruskal,再说一说prim,prim和迪杰斯特拉等一样,也是采用蓝白点的思想,蓝表示尚未进入生成树,白表示已经进入生成树,下面来说一下流程
1.初始化:min[v]=1<<30,min[1]=0,ans=0.
2.循环
(1)寻找min[u]最小的蓝点u。
(2)将u标记为白点,ans+=min[u]。
(3)遍历与u相邻的蓝点,若w[u][v]<min[v],则w[u][v]=min[v]
3.算法结束,ans即为最小生成树的边权值之和,时间复杂度为O(n^2)。
画图演示一下过程
1.min[1]=0,min[2,3,4,5]=1<<30,ans=0.
2.将1变为白点修改2,3,4min值。
3.可知min[2]最小,所以将2变为白点,修改3,5的min值
4.可知min[3]最小,所以将2变为白点,修改4的min值,此时w[u][v]>min[v],所以不做修改
5.将4,5遍历可得到最小生成树。
上代码,时间:1200ms。
#include<bits/stdc++.h>
using namespace std;
int ww[5002][5002],qaq,n,m,minn[5002],tot=1,now,ans;
bool v1[5002];
int prim(){
while(tot<n){
qaq=1<<30,tot++;
for(int i=1;i<=n;i++)
if(!v1[i]&&minn[i]<qaq){
qaq=minn[i];
now=i;
}
ans+=qaq;
for(int i=1;i<=n;i++)
if(minn[i]>ww[now][i]&&!v1[i])
minn[i]=ww[now][i];
v1[now]=1;
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
memset(ww,0x3f,sizeof(ww));
for(int i=1,u,v,w;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
ww[u][v]=ww[v][u]=min(w,ww[u][v]);
}
for(int i=1;i<=n;i++)
minn[i]=ww[1][i];
v1[1]=1;
printf("%d",prim());
return 0;
}
下面来两道简单的题
1.局域网