最小生成树:
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边,最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
从图的定义以及性质来看,一个含有n个顶点的联通图,最小生成树的有n个顶点n-1条边,使得所有顶点间都有直接或间接的边相连,并且所有边的权值之和最小。
个人觉得看图列最清晰:
这里我们讲解两种求解椎小生成树的方法,kruskal(克鲁斯卡尔)或prim(普里姆):
kruskal:将所有的边按权值排序,然后依次选这则权值最小的,且满足条件的边加入到树中(满足条件及,加入该边后不会形成环路),直到加入n-1条边到树中,及得到最小生成树
这里我们用:
<span style="font-size:18px;">typedef struct{
int from; //起点
int to; //终点
int v; //权值
}Edge;</span>
表示图的边信息,当然图有多重表示方法,在这里使用这种表示方式来求取最小生成树,不过各种表示方式间的转换很简单就不讨论了。
先给出一段求最小生成树的完整代码(最小生成树题目):
<span style="font-size:18px;">#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;
typedef struct {
int u;
int v;
int w;
}Edge;
Edge E[30000];
bool compare(Edge a, Edge b){
return (a.w<b.w);
}
int main()
{
int m,n,i,ans;
int nodeset[305];
bool flag[305]={0};
while(~scanf("%d%d",&n,&m)){
for(i=1;i<=m;i++){
scanf("%d%d%d",&E[i].u, &E[i].v, &E[i].w);
}
for(i=1;i<=n;i++)
nodeset[i]=i;
sort(E+1,E+m+1,compare);
int nodeflag=1,k=1;
ans=0;
while(k<n){
if(!flag[E[nodeflag].u] && flag[E[nodeflag].v]){
ans+=E[nodeflag].w;
flag[E[nodeflag].u]= true;
nodeset[E[nodeflag].u]= nodeset[E[nodeflag].v];
nodeflag++;k++;
}
else if(flag[E[nodeflag].u] && !flag[E[nodeflag].v]){
ans+=E[nodeflag].w;
flag[E[nodeflag].v]=true;
nodeset[E[nodeflag].v]= nodeset[E[nodeflag].u];
nodeflag++;k++;
}
else if(!flag[E[nodeflag].u] && !flag[E[nodeflag].v]){
ans+=E[nodeflag].w;
flag[E[nodeflag].u]= true;
flag[E[nodeflag].v]= true;
nodeset[E[nodeflag].u]=nodeset[E[nodeflag].v];
nodeflag++;k++;
}
else{
if(nodeset[E[nodeflag].u]!=nodeset[E[nodeflag].v]){
ans+=E[nodeflag].w;
int temp=nodeset[E[nodeflag].u];
for(i=1;i<=n;i++)
if(nodeset[i]== temp)
nodeset[i]=nodeset[E[nodeflag].v];
nodeflag++; k++;
}
else
nodeflag++;
}
}
cout<<ans<<endl;
}
return 0;
}</span>
这里主要是就是加入边时判断是否会构成环路需要注意一下,
首先,我们假设在生成树种有0条边,0个顶点,原图所在集合中有n个顶点,m条边,当加入第一条边或,构成树的所在集合便有2个顶点,1条边,于是每次加入边时,只需要判断当前加入边的两个顶点是否都在当前最小生成树的同一个顶点集合中,若在这一定会构成环,反之这不会,我们将该边纳入(这里建议学习一下集合的相关数据结构),一次加入合法边,直到加入n-1条,及构成最小生成树。