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)。在边的排序上最费时。