最小生成树
最小生成树是在一个给定的无向图G(V,E)中求一棵树T,使得这棵树拥有图G中所有顶点,且所有边都是来自图G的边,并且满足整棵树的边权和最小。
最小生成树的性质:
(1)最小生成树是树,其边数等于顶点数减1,其树内一定不会有环。
(2)对给定的图G(V,E),其最小生成树可以不唯一,但其边权之和一定是唯一的。
求解最小生成树的两种算法:prim算法和kruskal算法,都采用了贪心法的思想,只是贪心的策略不一样。
prim算法 用于稠密图(边多)
kruskal算法(克鲁斯卡尔算法)用于稀疏图(边少)
并查集
采用边贪心的策略
定义结构体:
sturct edge{
int u,v; //边的两个端点编号
int cost; //边权
} E[MAXE]; //最多有MAXE条边
写一个排序函数让数组E按边权从小到大排序:
bool cmp(edge a,edge b) //sort的自定义排序cmp函数 头文件:algorithm
{
return a.cost < b.cost; //从小到大排序
}
伪代码:
//kruskal函数返回最小生成树的边权之和,参数n为顶点个数,m为图的边数
int kruskal(int n,int m)
{
//ans为所求边权之和,Num_Edge为当前生成树的边数
int ans=0,Num_Edge=0;
for(int i=1;i<=n;i++){ //假设题目中顶点范围是[1,n]
father[i]=i; //并查集初始化
}
sort(E,E+m,cmp); //所有边按边权从小到大排序
for(int i=0;i<m;i++) //枚举所有的边
{
int faU = findfather(E[i].u);
int faU = findfather(E[i].v);
if(faU != faV) //如果不在一个集合中
{
father[faU] = faV; //合并结合(即把测试边加到最小生成树中)
ans += E[i].cost; //边权之和增加测试边的边权
Num_Edge++; //当前生成树的吧边数加1
if(Num_Edge == n-1)
break;
}
}
if(Num_Edge != n-1)
return -1; //无法连通时返回-1
else return ans;
}
kruskal算法的时间复杂度主要来源与对边进行排序,因此其时间复杂度是O(ElogE),其中E为图的边数。
算法笔记的例子(注意顶点编号范围是[0,n-1])
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXV = 110;
const int MAXE = 10010;
//边集定义部分
struct edge{
int u,v; //边的两个端点编号
int cost; //边权
} E[MAXE]; //最多有MAXE条边
bool cmp(edge a,edge b) //sort的自定义排序cmp函数 头文件:algorithm
{
return a.cost < b.cost; //从小到大排序
}
//并查集部分
int father[MAXV]; //并查集数组
int findFather(int x){ //并查集查询函数
int a = x;
while(x != father[x])
{
x=father[x];
}
//路径压缩
while(a != father[a])
{
int z=a;
a=father[a];
father[z] = x;
}
return x;
}
//kruskal函数返回最小生成树的边权之和,参数n为顶点个数,m为图的边数
int kruskal(int n,int m)
{
//ans为所求边权之和,Num_Edge为当前生成树的边数
int ans=0,Num_Edge=0;
for(int i=0;i<n;i++){ //假设题目中顶点范围是[1,n]
father[i]=i; //并查集初始化
}
sort(E,E+m,cmp); //所有边按边权从小到大排序
for(int i=0;i<m;i++) //枚举所有的边
{
int faU = findFather(E[i].u);
int faV = findFather(E[i].v);
if(faU != faV) //如果不在一个集合中
{
father[faU] = faV; //合并结合(即把测试边加到最小生成树中)
ans += E[i].cost; //边权之和增加测试边的边权
Num_Edge++; //当前生成树的吧边数加1
if(Num_Edge == n-1)
break;
}
}
if(Num_Edge != n-1)
return -1; //无法连通时返回-1
else return ans;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m); //顶点数、边数
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].cost); //两个端点编号、边权
}
int ans = kruskal(n,m); //kruskal算法入口
printf("%d\n",ans);
return 0;
}