最小生成树算法

最小生成树简介

简单说就是在一个带权连通图(一般是无向图)里面生成一个树,使得所生成的树具有最小的权重之和,谓之最小生成树,这点很容易理解,因为我们在构建树的时候,不存在环,所以图的任意两个顶点之间最多存在一条边,所以到最后生成的树一定具备的特征是:删减了一部分原来图当中的edge,这个也就是我们要的结果:生成权重之和最小的树,也就是最小生成树,基于最小生成树的特征,可以预见的是,最小生成树不一定是一个二叉树

最小生成树的两个算法

1. Prime算法

  • 思想很简单,就是在一个给定的图当中我们随意指定一个起始点作为当前MST的根,然后依次在找到权值最小的边连接加入到树当中去,直至整个图的节点加入完毕。该算法从顶点的角度为出发点,时间复杂度为\(O(n^2)\),更适合与解决边的绸密度更高的连通网。

    算法分析过程在博客链接里面。

  • 代码

#include<iostream>
#include<fstream>
using  namespace std;
 
#define MAX 100
#define MAXCOST 0x3f3f3f3f
 
int graph[MAX][MAX];
 
int prim(int graph[][MAX], int node)
{
	int lowcost[MAX];
	int mst[MAX];
	int i, j, min, minid, sum = 0;
	for (i = 2; i <= node; i++)
	{
		lowcost[i] = graph[1][i];
		mst[i] = 1;
	}
	mst[1] = 0;
	for (i = 2; i <= node; i++)
	{
		min = MAXCOST;
		minid = 0;
		for (j = 2; j <= node; j++)
		{
			if (lowcost[j] < min && lowcost[j] != 0)
			{
				min = lowcost[j];
				minid = j;
			}
		}
		cout << "V" << mst[minid] << "-V" << minid << "=" << min << endl;
		sum += min;
		lowcost[minid] = 0;
		for (j = 2; j <= node; j++)
		{
			if (graph[minid][j] < lowcost[j])
			{
				lowcost[j] = graph[minid][j];
				mst[j] = minid;
			}
		}
	}
	return sum;
}
 
int main()
{
//注意我们的节点从1开始的
	int i, j, k, m, n;
	int cost;
	ifstream in("input.txt");
	in >> m >> n;//m=顶点的个数,n=边的个数
	//初始化图G
	for (i = 1; i <= m; i++)
	{
		for (j = 1; j <= m; j++)
		{
			graph[i][j] = MAXCOST;
		}
	}
	//构建图G
	for (k = 1; k <= n; k++)
	{
		in >> i >> j >> cost;
		graph[i][j] = cost;
		graph[j][i] = cost;
	}
	//求解最小生成树
	cost = prim(graph, m);
	//输出最小权值和
	cout << "最小权值和=" << cost << endl;
	system("pause");
	return 0;
}

2. Kruskal算法

  • 基于并查集的一个算法,同时利用贪心策略,并查集在之前介绍并查集
    如果说prime算法是一个指定树的根然后递进延伸的话,那么Kruskal算法就是另外一个思想:分治、归并,也就是并查集的思想,基本的思路是:
    首先找到两个node,符合这两个node之间的路径长度最小,依次排序从小到大类推,知道所有的node全部都加入连接当中,连接结束的标志就是,在n个node之间连接了n-1条edge,那么我们构建的最小生成树也就随即完成了,因为本算法跟前面的prime不太一样,前面是一个连贯的递进过程,这个过程是一个分治连接的过程,我们需要在连接的各个部分之间判断是否存在环,如果是的话,应该舍弃连接,整个过程符合并查规律。

  • 基于上述描述,大致的代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,tot=0,k=0;//n端点总数,m边数,tot记录最终答案,k已经连接了多少边 
int fat[200010];//记录集体老大 
struct node
{
	int from,to,dis;//结构体储存边 
}edge[200010];
bool cmp(const node &a,const node &b)//sort排序(当然你也可以快排) 
{
	return a.dis<b.dis;
}
int father(int x)//找集体老大,并查集的一部分 
{
	if(fat[x]!=x)
	return father(fat[x]);
	else return x;
}
void unionn(int x,int y)//加入团体,并查集的一部分 
{
	fat[father(y)]=father(x);
}
int main()
{
	scanf("%d%d",&n,&m);//输入点数,边数 
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].dis);//输入边的信息 
	}
	for(int i=1;i<=n;i++) fat[i]=i;//自己最开始就是自己的老大 (初始化) 
	sort(edge+1,edge+1+m,cmp);//按权值排序(kruskal的体现) 
	for(int i=1;i<=m;i++)//从小到大遍历 
	{
		if(k==n-1) break;//n个点需要n-1条边连接 
		if(father(edge[i].from)!=father(edge[i].to))//假如不在一个团体 
		{
			unionn(edge[i].from,edge[i].to);//加入 
			tot+=edge[i].dis;//记录边权 
			k++;//已连接边数+1 
		}
	}
	printf("%d",tot);
	return 0;
}
posted @ 2020-12-04 11:53  Marvel_Iron_Man  阅读(603)  评论(0编辑  收藏  举报