图论(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;
}
posted on 2020-07-31 21:31  月下诺  阅读(158)  评论(0编辑  收藏  举报

Live2D