图论(2)--论最小生成树
树状数组??树...树.树呢?? --\(zyx\)
上回书说到,简单的最小路问题,那么这次我们来谈谈最小生成树问题.
最小生成树是指在固定的\(n\)个点,\(m\)条边的图中,找出\(n-1\)条边,使组成的图的边权最小且每个点之间可以相互到达(不一定是直接到达可以通过点的转移).
我们知道由\(n\)个点,\(n-1\)条边组成的图不是一颗树吗??所以这就是我们所要探讨的话题--最小生成树的名字的来历.
其实问题并不难,简单的来说,就是找\(n-1\)条边,使构成的图里没有环,且边权和最小.对于这个问题 我们提出两种方法也是最为普遍的两种方法
Part 1 Prim算法
利用一种很NB的思想--红黑树,
思路
设图的顶点集合为U,树的顶点集合为V
从图中任意一点出发,找到N-1条边(x,y),x∈U,y∈V,且权值最小。
通俗的讲,就是不断找权值最小且不产生闭环的N-1条边
举例子
话不多说,先上图
(1)从\(V3\)出发
(2)找到边(\(V3,V1\)),符合条件且最小,将\(V1\)加入\(V\)
以此类推……
(N)找到边\((V2,V5)\),符合条件且最小,将\(V5\)加入\(V\),最小生成树构造完成
/*Prim核心代码*/
for (int i=2;i<=n;i++)
{
lowcost[i]=a[i][1];//将与V1(或任意一点)有关的边存入lowcost(与各点最小权值)
}
for (int i=1;i<n;i++)
{
minval=1000000;//初始化最小值为正无穷
for (int j=1;j<=n;j++)
{
if (lowcost[j]>0&&lowcost[j]<minval)//如果当前权值不为0(即未连接过)且更小
{
k=j;//记录当前点
minval=lowcost[j];//将最小值存入
}
}
ans+=minval;//统计最小生成树最小权值和
lowcost[k]=0;//标记该点
for (int j=1;j<=n;j++)
{
if (lowcost[j]>0&&lowcost[j]>a[k][j])//由于U集合点增加,需更新与各点最小权值边
{
lowcost[j]=a[k][j];
}
}
}
Part 2 \(Kruskal\)算法
这个算法就充分的利用了并查集.
思路
利用并查集的思想,在最初把所有的点的祖先定为自己,并将边按全职大小从小到大排序,遍历所有边,如果两端点的祖先不相同,则合并集合,\(ans\)加上边权的大小,同时计数器\(++\) 等到计数器\(=n-1\)的时候结束循环,则很容易得到答案;
感性/理性证明
不确定是哪一种的证明方式
最小表示边权最小,所以我们只要排序后找前\(n-1\)条边,若有重边或存在环等不满足树的行为,则去掉这条边,加上第\(n\)条边即可,最终的答案一点是最小的.
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 500005
using namespace std;
struct node{
int x,y,z;
}edge[maxn];//从x->y 的边权为z的边
int fa[maxn],n,m,ans;
bool operator <(node x,node y)
{
return x.z<y.z;
}
int get(int x)
{
if(x==fa[x]) return x;
return fa[x]=get(fa[x]);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z);
}
int cnt=1;
sort(edge+1,edge+m+1);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
if(cnt == n)
{
printf("%d\n",ans);
return 0;
}
int x=get(edge[i].x),y=get(edge[i].y);
if(x==y) continue;
fa[x]=y;
cnt+=1;
ans+=edge[i].z;
}
if(cnt!=n) printf("orz");
return 0;
}