最小生成树——Kruskal算法---出自南昌理工学院ACM集训队
最小生成树
最小生成树:简单来说就是,带权图中遍历所有点所经过边权之和最小;
带权图:边赋以权值的图称为带权图(生成树的各边的权值总和称为该树的权);
注:最小生成树中不形成回路,联通n个点恰巧经过n-1条边
Kruskal算法
算法思想
贪心选取最短的边来组成一颗生成树(借助并查集实现);
算法简介
kruskal算法:取出带权图中所有带权边,并对它们进行排序,依次取出权值最小的边;
若此边与之前选取的边(存放在集合T中)无法形成回路,则将该边存入集合T中;
直至建到一颗生成树(即T中存入n-1条边);
算法复杂度
kruskal算法复杂度只与网中边的条数有关,与顶点个数无关,因此时间复杂度主要由排序方法决定,
因此当网的顶点个数较多、而边的条数较少时,使用克鲁斯卡尔算法构造最小生成树效果较好;
(复杂度O(m*lg(m)适用于稀疏图)
算法实现
例题(模板题,出自luoguP3366):
存储方式(结构体存储):
struct lq{
int x,y,z;
}f[200005];
排序部分(就直接用c++的快排了):
bool cmp(lq a,lq b){
return a.z<b.z;
}
sort(f,f+m,cmp);//以z的大小为标准对结构体进行排序
并查集部分(炒鸡普通。。。)
int pre[5005];
int father(int x){//找根结点
int root=x;
while(pre[root]!=-1){
root=pre[root];
}
return root;
}
bool un(int x,int y){//判断回路(若x,y是否在同一个集合中,不是则将结点y放入x所在的树中)
int x_root=father(x);
int y_root=father(y);
if(x_root==y_root){
return 0;
}
else{
pre[x_root]=y_root;
return 1;
}
}
完整代码(C++):
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<unordered_map>
#include<string>
#include<stack>
typedef long long ll;
using namespace std;
int n,m;//节点数,边数
int ans=0;//最小生成树边权和
int tot=0;//已经选取的边数
struct lq{
int x,y,z;
}f[200005];
int pre[5005];//并查集数组
bool cmp(lq a,lq b){
return a.z<b.z;
}
int father(int x){//查找根节点
int root=x;
while(pre[root]!=-1){
root=pre[root];
}
return root;
}
bool un(int x,int y){
int x_root=father(x);
int y_root=father(y);
if(x_root==y_root){
return 0;
}
else{
pre[x_root]=y_root;
return 1;
}
}
int main()
{
memset(pre,-1,sizeof(pre));//数组清零
cin>>n>>m;
for(int i=0;i<m;i++){
cin>>f[i].x>>f[i].y>>f[i].z;
}//读入数据
sort(f,f+m,cmp);//以z的大小为标准进行升序排序
for(int i=0;i<m;i++){//依次取出权值最小的边
if(un(f[i].x,f[i].y)){//如果该边的两个结点不在一个集合中,即添加该边未产生回路
ans+=f[i].z;//加入边权值
tot++;//边数加一
if(tot==n-1){//当边数为n-1时,输出权值和
cout<<ans;
return 0;
}
}
}
cout<<"orz";//出循环则tot<n-1,即该图不连通,输出“orz”
return 0;
}