最小生成树简介
简单说就是在一个带权连通图(一般是无向图)里面生成一个树,使得所生成的树具有最小的权重之和,谓之最小生成树,这点很容易理解,因为我们在构建树的时候,不存在环,所以图的任意两个顶点之间最多存在一条边,所以到最后生成的树一定具备的特征是:删减了一部分原来图当中的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;
}