最小生成树之Kruscal(克鲁斯卡尔),Prim 算法从入门到入坑 完整版

最小生成树:连通图中的权值总和最小的生成树。

Kruscal(克鲁斯卡尔)算法

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
整体思想是用并查集

数据存储:
自定义DSU并查集类
自定义Edge类(点a,点b,weight)
将edge作为参数放进Kruscal函数内

  1. 把图中的所有边按代价从小到大排序;
  2. 遍历所有边,当边两点对应两棵不相连的树,合并树
  3. 重复即可

在这里插入图片描述
在这里插入图片描述

package MST;

import java.util.Arrays;
import java.util.Scanner;

//2021年3月20日下午4:15:08
//writer:apple
public class Kursacl1 {
	
	static int n;
	static int m;//m条边
	
	static class DSU{//并查集
		int root[];
		int size[];
		
		public DSU(int n)
		{
			root=new int[n];
			size=new int[n];
			for(int i=0;i<n;i++)
			{
				root[i]=i;
			}
			Arrays.fill(size, 1);
		}
		
		public int find(int x)
		{
			if(root[x]!=x)
			{
				root[x]=find(root[x]);
			}
			return root[x];
		}
		
		public void union(int x,int y)
		{
			int rootX=find(x);int rootY=find(y);
			if(rootX==rootY) return;
			else {
				if(size[rootX]<size[rootY])//让size大的当父结点
				{
					root[rootX]=rootY;
					size[rootY]++;
				}
				else {
					root[rootY]=rootX;
					size[rootX]++;
				}
			}
		}
	}
	static class Edge {
		int a;
		int b;
		int weight;
		public Edge(int aa,int bb,int w)
		{
			a=aa;
			b=bb;
			weight=w;
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Scanner s=new Scanner(System.in);
		n=s.nextInt();m=s.nextInt();
		Edge edges[]=new Edge[m];
		
		for(int i=0;i<m;i++)
		{
			int a=s.nextInt();
			int b=s.nextInt();
			int w=s.nextInt();
			edges[i]=new Edge(a,b,w);
		}
		Arrays.sort(edges,(a,b)->a.weight-b.weight);//根据边的权值从小到大排序
//		for(int i=0;i<m;i++)
//		{
//			System.out.println(edges[i].weight);
//		}
		int mintree=kruscal(edges);
		System.out.println(mintree);
	}
	
	private static int kruscal(Edge edges[])
	{
		int mintree=0;
		DSU dsu=new DSU(10005);//构造方法
		for(Edge x:edges)
		{
			int a=x.a;
			int b=x.b;
			if(dsu.find(a)!=dsu.find(b))
			{
				mintree+=x.weight;
				dsu.union(a, b);
			}
		}
		return mintree;
	}
}

模板题:
P3366

import java.util.Arrays;
import java.util.Scanner;

//2021年3月20日下午5:12:52
//writer:apple
public class Main {

	static int n;
	static int m;
	
	static class DSU{
		int root[];
		int size[];
		public DSU(int n)
		{
			root=new int[n];
			size=new int[n];
			for(int i=0;i<n;i++)
			{
				root[i]=i;
			}
			Arrays.fill(size,1);
		}
		
		public int find(int x)
		{
			if(root[x]!=x)
			{
				root[x]=find(root[x]);
			}
			return root[x];
		}
		
		public void union(int x,int y)
		{
			int rootX=find(x);int rootY=find(y);
			if(rootX==rootY) return ;
			if(size[rootX]<size[rootY])
			{
				root[rootX]=rootY;
				size[rootY]++;
			}
			else {
				root[rootY]=rootX;
				size[rootX]++;
			}
		}
	}
	
	
	static class Edge{
		int a;
		int b;
		int weight;
		public Edge(int aa,int bb,int w)
		{
			a=aa;
			b=bb;
			weight=w;
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner s=new Scanner(System.in);
		n=s.nextInt();m=s.nextInt();
		Edge edges[]=new Edge[m];
		for(int i=0;i<m;i++)
		{
			int a=s.nextInt();int b=s.nextInt();int w=s.nextInt();
			edges[i]=new Edge(a,b,w);
		}
		Arrays.sort(edges,(a,b)->a.weight-b.weight);//升序
		int mintree=kruscal(edges);
		if(mintree!=0)
		System.out.println(mintree);
		else {
			System.out.println("orz");
		}
		}
	
	
		public static int kruscal(Edge edges[])
		{
			int mintree=0;
			int depth=0;
			DSU dsu=new DSU(200005);
			for(Edge x:edges)
			{
				int a=x.a;
				int b=x.b;
				int w=x.weight;
				if(dsu.find(a)!=dsu.find(b))
				{
					dsu.union(a, b);
					depth++;
					mintree+=w;
				}
			}
			if(depth==n-1) return mintree;
			else return 0;
		}
}

Prim 算法

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

数据存储&步骤:

1,自定义pair类(属性有 weight,相邻点x)
2,建图,邻接链表,创建Map<Integer,LinkedList> g=new HashMap<>();用于存储相邻结点和权值。(用linkedlist建邻接链表也行)
3,运用优先队列,存储相邻结点和权值,弹出相邻结点中,未访问过的且权值最小的结点
4,直到优先队列为空,结束,求得最小生成树
模板题:
P3366

package MST;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.PriorityQueue;

import MST.Prim1.pair;

//2021年3月21日下午1:42:52
//writer:apple
public class prim_MLE {
	static int n;
	static int m;
	static class pair{
		int weight;
		int x;
		public pair(int w,int xx)
		{
			weight=w;
			x=xx;
		}
	}
	static Map<Integer,LinkedList<pair>> g=new HashMap<>();
	static PriorityQueue<pair> heap=new PriorityQueue<>((a,b)->a.weight-b.weight);
	
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
		st.nextToken();n=(int)st.nval;
		st.nextToken();m=(int)st.nval;
		
		for(int i=0;i<m;i++)
		{
			st.nextToken();int a=(int)st.nval;
			st.nextToken();int b=(int)st.nval;
			st.nextToken();int w=(int)st.nval;
			g.putIfAbsent(a,new LinkedList<>());
			g.putIfAbsent(b,new LinkedList<>());
			g.get(a).add(new pair(w,b));
			g.get(b).add(new pair(w,a));
		}
		int mintree=prim(1,g);
		PrintWriter pr=new PrintWriter(new OutputStreamWriter(System.out));
		pr.println(mintree);
		pr.flush();
	}
	public static int prim(int x,Map<Integer, LinkedList<pair>> g)
	{
		heap.add(new pair(0,x));//先把x结点(起点)放进去,权值为0
		boolean vis[]=new boolean[n+1];//根据点的数量n 开空间
		int mintree=0;
		while(!heap.isEmpty())
		{
			pair p=heap.poll();//弹出队首元素,为相邻点中边权最小的元素
			int w=p.weight;int y=p.x;//y为结点
			if(vis[y]) continue;
			vis[y]=true;
			mintree+=w;
			for(int i=0;i<g.get(y).size();i++)
			{
				pair nextp=g.get(y).get(i);//为下一个结点的pair
				if(!vis[nextp.x])
				{
					heap.add(nextp);
				}
			}
		}
		return mintree;
	}
}

Kruscal算法和Prim算法区别:

两种算法都是贪心思想:

Prim:N个点,寻找的是下一个与MST中任意顶点相距最近(把权值看做距离)的顶点,只需要循环N-1次就够了,而且只能是N-1次(因为有N-1条边),否则可能会发生错误(视INF而定),这就是Prim算法。

Kruskal:N个点,算法寻找的是每次权值尽可能小的边,然后看看该边的两个点是否与树矛盾(用并查集判断就行了),在加边的过程中记录有效点的数目,达到N个(因为有N点点)就结束,不用把每条边都考虑进去。

Prim算法的时间复杂度分析与Dijkstra算法相同,都是 O(|V^2|)

Prim,Prim_heap,Kruskal算法时间复杂度比较

1.Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。

2.Prim_heap在任何时候都有令人满意的的时间复杂度,但是代价是空间消耗极大。(以及代码很复杂>_<)

但值得说一下的是,时间复杂度并不能反映出一个算法的实际优劣。

竞赛题一般给的都是稀疏图,选择Prim_heap即可;如果觉得代码量太大,想要在Prim与Kruskal算法中选一个,那就选择Kruskal算法,虽然不快,又省空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值