最小生成树--Kruskal算法

        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
*/ 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值