C++学习第四篇——最小生成树

最小生成树作为图论的重要算法之一,难度不言而喻,那么只要把握这几种算法,就很容易完成啦。但要区分于单源最短路和多源最短路,最小生成树是没有初始结点的,也就是它只求连通图,可以从任意点开始连。

算法1:Kruskal

Kruskal算法本质类似于并查集,将所有点连通至一个集合完成整个图的连接,这里很重要的一点就是并查集的压缩路径,没有压缩过的时间复杂度会变得很高。并查集的压缩路径可以看一下我的另一篇博客,并查集的模板这里强调一点并查集的初始化很重要,不要忘记

struct node
{
	int x,y,weight;
}node[1000000]; //用来存储每条边的值和端点
int arr[1005];//并查集数组
void Union(int a,int b){}//连接结点 
int find(int a){}//寻找头结点

最小生成树顾名思义我们需要找出最短值的边使整个集合连通,那么我们可以根据已给出的所有边的值进行排序。

bool cmp(struct node a,struct node b)
{
	return a.weight<b.weight;
}

然后调用sort函数,sort(node,node+m,cmp),m在这里指的是边的数量。
接下来,我们将边从小到大进行连接,判断条件为他们是否在同一个集合内,因为一旦在同一个集合内就表明,在此边之前有一个比它更小的边完成了这两个点的连接,已经不需要了,这里的连接并不是直连,而是在同一集合内的连接。所以每一条边,我们判断它的端点是否已经在同一集合内,因为边的值会越变越大,我们只需要判断连接即可。那么降低时间复杂度的一点就是,如果这个图已经完成了连接,也就是所有点的顶点相同,那么就可以结束对边的循环。又或者说只需要n-1条边即可,因为对于n个顶点的连通图,我们仅需要n-1条边,那么一旦出现n-1条边,我们就可以结束循环。所以最好的情况,Kruskal的循环只需要n-1次,最坏的情况需要m次。但这里并没有包括并查集的复杂度,所以整体最好还是会达到O(n^2),最坏O(n*m);
接下来,Kruskal模板

int Kruskal()
{
	int sum=0,num=0for(int i=0;i<m;i++)
	{
		if(find(node[i].x)!=find(node[i].y))//判断是否在同一集合内
		{
			Union(node[i].x,node[i].y);//连接两个端点
			sum+=node[i].weight;//加上这条边的权值
			num++;//连接边数+1
		}
		if(num==n-1) break;
	}
	return sum;
}
算法2:prim

相比如Kruskal算法,prim算法比较难懂,但是理解过后便会对最短路有了更清晰的认识,有利于向Dijkstra算法的延伸。Dijkstra算法
由于技术有限,只能通过文字讲解,所以直接上代码,结合代码理解最佳。

int pic[1005][1005];//存图 

在输入之前,初始化图,将各点之间的距离设置为最大值,表示无法到达。

void init()
{
	for(int i=0;i<1005;i++)
	{
		for(int j=0;j<1005;j++)
		{
			pic[i][j]=(1<<21);//最大值可以自己设定,只要能表示不可达即可
		}
	}
}

接着对图进行赋值,区别于Kruskal这里的值是双向的。
prim模板函数如下

void prim()
{
	int weight[1005];//起始到其他端点的值 
	for(int i=0;i<n;i++)
	{
		weight[i]=pic[0][i];//将图中0到各个端点的值赋给weight数组 
	}
	pic[0][0]=0;
	weight[0]=0;//表明0到0之间的距离为0 
	ll sum=0;//用来求距离和 
	//此时起点为0
	for(int i=1;i<n;i++)//0点作为起始点,从下一个点开始 
	{
		int j=0,k=0,min=(1<<31)-1;
//weight内的值为0时表示,已经访问过 
//遍历weight数组,寻找一个最小值,并且该点没有被访问过 
		while(j<n)
		{
			if(min>weight[j]&&weight[j]!=0)
			{
				min=weight[j];
				k=j;//用k存下这个节点 
			}
			j++;
		} 
//已知的min是我当前起始节点到k节点的距离,已知连接边中最短的 
		sum+=min;
		weight[k]=0;//访问过该节点 
//每一个k的值就是连接顺序的点。
//自此形成从起始到K的边,将其连接
//更新weight数组,一旦新点的连通其他点的值小于原来节点连通值时,更新 
 		for(int m=1;m<n;m++)
		{
		 	if(weight[m]>pic[k][m]&&weight[m]!=0)
		 	{
		 		weight[m]=pic[k][m]; 
			}
		} 
	} 
}

weight数组是最难理解的一个部分,它实际上表明的是当前这一集合到剩余点的距离,举个例子,0是起始点,短-表示连接,0-1=2,0-2=3,0-3=4,weight[1]=2,weight[2]=3,weight[3]=4。那么我会取1进行连接。接下来1-2=1,1-3=3,此时比较与0-2,我会发现1-2更短,而0和1其实已经是一个整体,那么这个整体到2的连接最短无疑是1-2,于是将weight[2]内的值替换为1,weight[3]同理。时间复杂度达到了O(n^2)。

算法3:Sollin

这个算法同样适用于最小生成树,类似于prim和并查集的集合体,相比于前两种主流的算法,优势并不大,所以不过多介绍了。Sollin模板,并查集部分自行补全。

struct node
{
	int u;
	int v;
	int w;
}edge[105];
int d[105];//各子树的最小连外边的权值
int e[105];//各子树的最小连外边的索引
bool visit[105];//防止边重复统计
int fa[105];
int Boruvka()
{
	int tot=0;
	for(int i=1;i<=n;i++) fa[i]=i;
	while(true)
	{
		int cur=0;
		for(int i=0;i<n;i++) d[i]=(1<<21);
		for(int i=0;i<m;i++)
		{
			int a=find(edge[i].u),b=find(edge[i].v),c=edge[i].w;
			if(a==b) continue;
			cur++;
			if(c<d[a]||c==d[a]&&i<e[a]) d[a]=c,e[a]=i;
			if(c<d[b]||c==d[b]&&i<e[b]) d[b]=c,e[b]=i;
		}
		if(cur==0) break;
		for(int i=0;i<n;i++)
		{
			if(d[i]!=inf&&!visit[e[i]])
			{
				join(edge[e[i]].u,edge[e[i]].v);
				tot+=edge[e[i]].w;
				visit[e[i]]=true;
			}
		}
	}
	return tot;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值