最小生成树问题的讲道理都是很直观的,让人一眼看得穿,然后再在模版上稍加改动就完成了。
一般来说Prim跟Kruskal方法都是能解题的。但是复杂度不一样。
Prim的时间复杂度是O(n^2),而Kruskal方法与给出的边的数量有关,复杂度为O(eloge)其实时间都是花在排序上了 。
1.Prim算法
简单来说,就是从一个点开始不断寻找,链接着其他相关点,每次加入一个距离最小的点。
其中low数组就是保存到其他点的距离,vis保证每个点只经过一次。
另外求最大生成树类似,只要将输入的数加上负号,最后输出的时候再加上负号。
#include<iostream>
#include<string.h>
using namespace std;
#define N 105
#define inf 0x3f3f3f3f
int low[N],vis[N],G[N][N];
int x,n,ans;
int prim()
{
int pos,minnum,result=0;
memset(vis,0,sizeof(vis));
vis[1]=1;
pos=1;
//从第一个定点开始也可从指定定点开始
for(int i=1; i<=n; i++)
{
if(i!=pos)low[i]=G[pos][i];
}
//更新low数组
for(int i=1; i<n; i++)//再运行n-1次
{
minnum=inf;
for(int j=1; j<=n; j++)
{
if(vis[j]==0&&minnum>low[j])
{
minnum=low[j];
pos=j;
}
}
//if(minnum==inf)break;前面在加cnt变量,可以判断是否能成功生成最小生成树
result+=minnum;//找到最小值与下一点
vis[pos]=1;
for(int i=1; i<=n; i++)
{
if(vis[i]==0&&low[i]>G[pos][i])
low[i]=G[pos][i];
}//更新low数组
}
return result;
}
int main()
{
while(cin>>n)
{
memset(G,inf,sizeof(G));//赋初始值inf
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
{
cin>>x;
G[i][j]=G[j][i]=x;
//最大生成树则是把权值改成负数,最终结果再加个负号
}
ans=prim();
cout<<ans<<endl;
}
return 0;
}
2.Kruskal算法
首先相对Prim不同的是,这是一种针对边来解决问题的一种方法,因此,定义结构,保存边的两点,权重等信息。
另外,要求最小,必然对权重排序。
然后不断纳入新的联通分量的点。(并查集知识)
#include<iostream>
#include<algorithm>
using namespace std;
struct Edge
{
int u,v,w;
} edge[105];//结构体包括边的两个连接点和权重
int parent[105];
int findpa(int x)
{
return parent[x]==x?x:parent[x]=findpa(parent[x]);
}//查找是否在同一个连通分量中
bool cmp(Edge a,Edge b)
{
return a.w<b.w;
}//按权值排序
void Init()
{
for(int i=1; i<=105; i++)
parent[i]=i;//开始每个都是一个单独的联通分量
}
int main()
{
int n;
cin>>n;
Init();
for(int i=0; i<n; i++)
cin>>edge[i].u>>edge[i].v>>edge[i].w;
sort(edge,edge+n,cmp);
int ans=0;
for(int i=0; i<n; i++)
{
int x=findpa(edge[i].u);
int y=findpa(edge[i].v);
if(x!=y)//判断两点是否在同一个连通分量中,是就不要,不是就纳入
{
parent[y]=x;
ans+=edge[i].w;
}
}
cout<<ans<<endl;
return 0;
}
前导知识:并查集
算法描述:并查集用来解决判断两个节点是否相连通,且不需要给出的具体路径(需要给路径另外考虑dfs等算法)
算法核心:指定唯一根节点,判断两节点是否有共同的根节点。单纯向上寻找可能很费时间,且因为不用保留路径因此可以使用路径压缩:既令同一个连通分量中的元素直接指向父节点。
核心代码 :简单模版void init()//一开始每个人都是独立一个连通分量
{
cnt = 0;
for (int i=0; i<=m; i++) p[i] = i;
}
int find(int x)//不断查找父节点
{
return p[x]==x ? x : p[x] = find(p[x]);
}
void merge(int a, int b)
{
int pa = find(a), pb = find(b);
p[pa] = pb;
}
PS:关于最小生成树的基本上都是在模版上大致改动。准备一个好用的模版很重要~