最小生成树

Prim算法

Prim算法和Dijkstra算法十分相似,都是从某个顶点出发,不断添加边的算法。

import java.util.*;
 
public class Main {
	/*
	 * 顶点从下标为0开始记
	 */
	static Scanner sc=new Scanner(System.in);
	static int n,m;//顶点的个数,边的个数
	static int[][] g;//图的邻接矩阵
	static int[] mincost;//树到各顶点的最短距离
	static boolean[] s;//已加入树的点的集合
	static int INF=Integer.MAX_VALUE;
	
	static void gInitialize(){//无向图的初始化
		n=sc.nextInt();
		m=sc.nextInt();
		g=new int[n][n];
		for(int i=0;i<g.length;i++)
			for(int j=0;j<g[i].length;j++)
				g[i][j]=INF;
		for(int i=0;i<m;i++){
			int x=sc.nextInt(),y=sc.nextInt(),cost=sc.nextInt();
			g[x][y]=cost;
			g[y][x]=cost;
		}
	}
	
	static int prim(){
		int res=0;//最小生成树的边权和
		mincost=new int[n];
		s=new boolean[n];
		for(int i=0;i<n;i++){
			s[i]=false;
			mincost[i]=INF;
		}
		mincost[0]=0;
		while(true){//此循环体仅用来限制计算次数,每次并入一个顶点
			int v=-1;//准备并入的顶点
			for(int i=0;i<n;i++){//从不属于s的顶点中选取从s到其权值最小的顶点
				if(!s[i]&&(v==-1||mincost[i]<mincost[v])){//注意这里与迪杰斯特拉算法的差别
					v=i;
				}
			}
			if(v==-1)//已经没有可以并入的顶点,算法结束
				break;
			s[v]=true;//把顶点v并入
			res+=mincost[v];
			for(int i=0;i<n;i++){//更新mincost
				mincost[i]=Math.min(mincost[i],g[v][i]);
			}
		}
		return res;
	}
	
	public static void main(String[] args){
		gInitialize();
		prim();
		System.out.println(prim());
	}
}

时间复杂度:O(V^2)。不过和Dijkstras算法一样,如果用堆来维护mincost时间复杂度就是O(ElogV)。


Kruskal算法(使用并查集优化)

Kruskal算法按照边的权值从小到大的顺序查看一遍所有边,如果不产生圈(重边也算在内),就把当前这条边加入到生成树中。

Q1.如何判断是否产生圈?

假设现在要把连接顶点v和顶点w的边e加入到生成树中。如果加入之前v和w不在一个连通分量里,那么加入e也不会产生圈。反之,如果v和w在同一个连通分量里,那么一定会产生圈。

Q2.怎么(为什么能)用并查集优化“判断是否产生圈”这一步?

如果两个节点在同一连通分量内,那么它们的根节点一定相同,反之一定不相同。利用这一点就可以使用并查集了。

import java.util.*;
 
public class Main {
	static Scanner sc = new Scanner(System.in);
	static int n,m;//节点数,边数
	static ArrayList<e> list=new ArrayList<e>();
	static class e{
		int v,w,c;
	}
	
	static class union_find_set{//并查集
		int[] f=new int[n+1];
		int[] rank=new int[n+1];
		void init(){
			for(int i=1;i<=n;i++){
				f[i]=i;
				rank[i]=1;
			}
		}
		int find(int x){
			if(f[x]==x)
				return x;
			else
				return f[x]=find(f[x]);
		}
		void merge(int x,int y){
			x=find(x);
			y=find(y);
			if(x!=y){
				if(rank[x]<rank[y])
					f[x]=y;
				else{
					f[y]=x;
					if(rank[x]==rank[y])
						rank[x]++;
				}
			}
		}
		boolean same(int x,int y){
			return find(x)==find(y);
		}
	}
	
	static int kruskal(){
		Collections.sort(list,new Comparator<e>(){
			public int compare(e e1,e e2){
				return e1.c-e2.c;
			}
		});
		union_find_set ufs=new union_find_set();
		ufs.init();
		int res=0;
		for(int i=0;i<m;i++){
			e e1=list.get(i);
			if(!ufs.same(e1.v,e1.w)){
				ufs.merge(e1.v,e1.w);
				res+=e1.c;
			}
		}
		return res;
	}
	
    public static void main(String[] args) {
    	n=sc.nextInt();
    	m=sc.nextInt();
    	for(int i=0;i<m;i++){
    		e e1=new e();
    		e1.v=sc.nextInt();
    		e1.w=sc.nextInt();
    		e1.c=sc.nextInt();
    		list.add(e1);
    	}
    	System.out.println(kruskal());
    }
}

时间复杂度:O(ElogV)。在边的排序上最费时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值