Kruskal算法将n个顶点看成是n个孤立的连通分支,首先将所有的边按权值从小到大排序,然后做贪心选择:
在边集E中选取权值最小的边(i, j),如果将边(i, j)加入集合TE中不产生回路(圈),则将边(i, j)加入边集TE中;否则继续选择下一条最短边。
怎样判断加入某条边后T中会不会出现回路呢?
Kruskal用了一个非常聪明的方法,即集合避圈法:
如果待选择边的起点和终点都在T的集合中,就可以判定形成回路(圈)。待选择边的两个端点不能属于同一集合。
算法设计
1)初始化。将图G的边集E中的所有边按权值从小到大排序,边集TE={ },每个顶点初始化一个集合号。
2)在E中寻找权值最小的边(i,j)。
3)如果顶点i和j位于两个不同连通分支,则将边(i,j)加入边集TE,并将两个连通分支进行合并。
4)将边(i,j)从集合E中删去,即E=E−{(i,j)}。
5)如果选取边数小于n−1,转步骤2;否则,算法结束。
算法实现
//program 2.7.1 kruskal算法
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1005;
int nodeset[N];
int n,m;
struct Edge{
int u,v,w;
}e[N*N];
bool cmp(Edge x,Edge y){//定义优先级,按边值进行升序排序
return x.w<y.w;
}
void init(int n){//每个结点初始化一个集合号
for(int i=1;i<=n;i++)
nodeset[i]=i;
}
bool merge(int a,int b){//合并集合
int p=nodeset[a];//p为a结点的集合号
int q=nodeset[b];//q为b结点的集合号
if(p==q) return false;//集合号相同,什么也不做,返回
for(int i=1;i<=n;i++){//检查所有结点,把集合号是q的改为p
if(nodeset[i]==q)
nodeset[i]=p;
}
return true;
}
int kruskal(int n){//求最小生成树
int ans=0,cnt=0;
for(int i=0;i<m;i++){
if(merge(e[i].u,e[i].v)){
ans+=e[i].w;
cnt++;
if(cnt==n-1)//选中n-1条边结束
return ans;
}
}
return 0;
}
int main(){
int t;
cin>>t;
while(t--){
cin>>n>>m;
init(n);
for(int i=0;i<m;i++)
cin>>e[i].u>>e[i].v>>e[i].w;
sort(e,e+m,cmp);
cout<<kruskal(n)<<endl;
}
return 0;
}
/*测试样例
1
7 12
1 2 23
1 6 28
1 7 36
2 3 20
2 7 1
3 4 15
3 7 4
4 5 3
4 7 9
5 6 17
5 7 16
6 7 25
*/
算法分析
(1)时间复杂度:边排序为O(mlogm),合并为O(n2) 。
(2)空间复杂度:辅助数组nodeset[],空间复杂度为O(n)。
算法优化
(1)时间复杂度:边排序为O(mlogm),合并为O(nlogn) 。
(2)空间复杂度:空间复杂度为O(n)。
//program 2.7.2 kruskal算法+并查集优化
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1005;
int fa[N];
int n,m;
struct Edge{
int u,v,w;
}e[N*N];
bool cmp(Edge x,Edge y){
return x.w<y.w;
}
void init(int n){//初始化集合号
for(int i=1;i<=n;i++)
fa[i]=i;
}
int find(int x){//查找x的集合号
if(x!=fa[x])//找祖宗
fa[x]=find(fa[x]);//把当前结点到其祖宗路径上的所有结点的集合号改为祖宗集合号
return fa[x];
}
bool merge(int a,int b){//合并集合
int p=find(a);
int q=find(b);
if(p==q) return false;
fa[q]=p;
return true;
}
int kruskal(int n){//求最小生成树
int ans=0,cnt=0;
for(int i=0;i<m;i++){
if(merge(e[i].u,e[i].v)){
ans+=e[i].w;
cnt++;
if(cnt==n-1)//选中n-1条边结束
return ans;
}
}
return 0;
}
int main(){
int t;
cin>>t;
while(t--){
cin>>n>>m;
init(n);
for(int i=0;i<m;i++)
cin>>e[i].u>>e[i].v>>e[i].w;
sort(e,e+m,cmp);
cout<<kruskal(n)<<endl;
}
return 0;
}
/*测试样例
1
7 12
1 2 23
1 6 28
1 7 36
2 3 20
2 7 1
3 4 15
3 7 4
4 5 3
4 7 9
5 6 17
5 7 16
6 7 25
*/