最小生成树:连通图中的权值总和最小的生成树。
Kruscal(克鲁斯卡尔)算法
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
整体思想是用并查集
数据存储:
自定义DSU并查集类
自定义Edge类(点a,点b,weight)
将edge作为参数放进Kruscal函数内
- 把图中的所有边按代价从小到大排序;
- 遍历所有边,当边两点对应两棵不相连的树,合并树
- 重复即可
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算法,虽然不快,又省空间。