最小生成树—prim和kruskal算法
一般Kruskal用的比prim多,因为prim可以解决的,kruskal一定可以解决,反过来就不一定了。
n是点的数量,m是边的数量。
prim时间复杂度:O(n^2) 还有一个像Dijkstra的堆优化版本,但是被Kruskal完爆,所以基本不会用。一般用在稠密图(边的数量接近n^2), 用邻接矩阵来存。
如果是用
kruskal时间复杂度:O(mlogm),存储边的时候不是用的邻接表,而是用三元组的形式存起来,方便进行排序。
prim算法
prim 算法采用的是一种贪心的策略。
每次将离连通部分的最近的点和点对应的边加入的连通部分,连通部分逐渐扩大,最后将整个图连通起来,并且边长之和最小。有点类似与Dijkstra算法。
代码思想和Dijk极其相似,只有dist的定义变了,变成了到集合的最短距离。
代码
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=510,INF=0x3f3f3f3f;
int n,m;
int g[N][N]; // 邻接矩阵
int dist[N];// 存放i顶点到集合的最短距离
bool st[N];// 判断i顶点是否在集合中了
int prim()
{
memset(dist,0x3f,sizeof dist);
int res=0;//存放最小生成树的长度
for(int i=0;i<n;i++)//n次遍历,每次加一个顶点到集合中
{
int t=-1;
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1||dist[t]>dist[j]))
t=j;//如果j不在集合中,并且j是第一个顶点或者j到集合的距离更小
if(i&&dist[t]==INF) return INF;//如果图不连通
if(i) res+=dist[t];//找到第二个顶点的时候就可以在结果上加上这个边长了
//以刚加入集合的点t,更新其他点到集合的最短距离。但是有点点有可能存在负的自环,dist[t]有可能一直变小,
//但是已经无所谓了,反正dist[t]在上一步已经被加进去了
for(int j=1;j<=n;j++) dist[j]=min(dist[j],g[t][j]);
st[t]=true;
}
return res;
}
int main()
{
scanf("%d%d",&n,&m);
memset(g,0x3f,sizeof g);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a][b]=g[b][a]=min(g[a][b],c);
}
int t=prim();
if(t==INF) puts("impossible");
else printf("%d\n",t);
return 0;
}
证明:
反证法:如果当前与外界直接相连的权值最小的一条边,不出现在最优解中,那么好啊,你再找一条权值最小的边连那个点呗,然后再把我这个边去掉,你这不还是生成树吗?但是不好意思,我已经是权值最小的边了,嘿嘿。
Kruskal算法
如果说prim是一个大的集合逐渐向外扩展,那么Kruskal就是多线作战,最后才合体。
代码思想:先存下来所有的边,然后对边权进行排序,优先连小的边,直至联通。判断是否联通和连边的操作利用了并查集。最后使用cnt判断连在一起的点数,如果小于n,则证明没有联通,不存在最小生成树。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 200010;
int n, m;
int p[N]; //并查集中的parent
struct Edge
{
int a, b, w;
bool operator<(const Edge &W) const
{
return w < W.w;
}
} edges[N];
//判断两个元素是否属于同一集合,看他们的根节点是否相同即可
int find(int x)//这个函数可以找到x的根节点
{
// if(x==p[x]) return x;
// else
// return find(p[x]);
// 路径压缩。
if (p[x] != x)//如果x的爸爸不等于x,
p[x] = find(p[x]);//
return p[x];//如果自己是自己的爸爸,返回自己
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++)
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
sort(edges, edges + m);
for (int i = 1; i <= n; i++)
p[i] = i;//并查集初始化
int res = 0, cnt = 0;
for (int i = 0; i < m; i++)
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a=find(a),b=find(b);//分别找打a,b的根节点
if(a!=b){//如果a,b不属于同一个集合,则合并
p[a]=b;//让b变成a的爸爸
res+=w;
cnt++;
}
}
if(cnt<n-1) puts("impossible");
else printf("%d\n",res);
return 0;
}
证明
连接两个不相连的集合,一定要找权值最小的啊。