一、Krusal算法简介
Krusal算法其实就是以边为主,每次都选择权重最小的并且两个端点不在同一个连通分量的那条边加进生成树T中,知道T的边达到了顶点数-1之后,就暂停。所以,可以先将边按从小到大进行排序,然后再去选取每一条边,判断它的两个端点是否是同一连通分量
二、Krusal算法实现步骤
输入格式:第一行有两个整型数,第一个数为顶点数,第二个数为边数N,接下来就要N行,每一行有3个字符,第一个为字母就是起点,第二个字母是终点,第三个数字是权重。
输出格式:输出一个整数,表示最小生成树的权重和
实现步骤如下:
1.先构建一个图的存储结构(利用套娃的思想)
①新建一个ArrayList数组,然后该数组是Edge类的,所以每一个元素都是Edge类的,所以可以自己自定义Edge类需要哪些东西
②读入数据,按照输入格式,建立一个Edge的结点,然后add到ArrayList里
2.Krusal算法实现
①新建一个Map容器,用来存每个顶点的连通分量的根节点,注意,这里用Map容器的原因是,输入的er顶点都是字符串的形式,只有Map才有字符串对字符串的键值对。然后对M进行初始化,即每个顶点对应赋值都是自己。
Parent.put(a,a)
②然后e将ArrayList数组根据每一个元素里面的weight大小进行升序排序
③接着开始遍历数组,每次都选择一条边,判断这个边两个端点是否是同一个连通分量,如果不是的话,就加进来,同时计算权重添加。注意第一次的时候,直接选取第一条边,把第一条边的两个顶点Union成一个连通分量,第二次开始才需要判断。
④因此,要加上并查表的两个操作:FindFather和Union
⑤最后,边数等于N-1,即循环次数等于N时,break。注意边数与循环次数不同。自己要在脑子过一遍那个循环过程
三、Krusal实现代码如下:
import java.util.*;
/*
* 输入格式:第一行有两个数,第一个数为顶点数,第二个数为边数N,接下来就要N行,每一行有3个数组,第一个数字就是起点,第二个数字是终点,第三个数字是权重
* 输出:最小生成树的权重和
*/
//这是一个无向图的边存储结构,因为输入格式中不会出现重复的边,因此在选择最小边时,只要分别两次判断判断头尾顶点是否的连通分支即可
class DirectedGraph {
private Map<String,String> Parent=new HashMap<>();//并查集用的
private List<Edge> directedGraph;
public DirectedGraph(int edges) {
directedGraph = new ArrayList<>();
buildGraph(edges);
}
private class Edge {
private String endVertex;
private String startVertex;
private int weight;
public Edge(String startVertex,String endVertex,int weight) {
this.endVertex = endVertex;
this.startVertex=startVertex;
this.weight=weight;
}
}
private void buildGraph(int edges) {
Scanner cin = new Scanner(System.in);
Edge e,e2;
for (int i = 0; i < edges; i++) {
String a = cin.next();
String b = cin.next();
int c= cin.nextInt();
e=new Edge(a,b,c);e2=new Edge(b,a,c);
if ((directedGraph.indexOf(e)==-1)&& (directedGraph.indexOf(e2)== -1)){
directedGraph.add(e);
}
}
}
public String FindFther(String x){
String a=x;
while(x!=Parent.get(x)){
x=Parent.get(x);
}
while(a!=Parent.get(a)){
String z=a;
a=Parent.get(a);
Parent.put(z,x);
}
return x;
}
public void Union(String a,String b){
String faA=FindFther(a);
String faB=FindFther(b);
if(faA!=faB){
Parent.put(faA,faB);
}
}
public int kruskal(int numbersofvertexs){
Collections.sort(directedGraph,new Comparator<DirectedGraph.Edge>(){
@Override
public int compare(DirectedGraph.Edge o1, DirectedGraph.Edge o2) {
return o1.weight-o2.weight;//升序排序
}
});
for(int i=65;i<91;i++){
char a=(char)i;
String s=String.valueOf(a);
Parent.put(s,s);
}
int count=1;int weights=0;
String k = null;
for(Edge e:directedGraph){
if(count==1){
Union(e.startVertex,e.endVertex);
weights=weights+e.weight;
count++;
}
else {
String edge1 = e.endVertex;
String edge2 = e.startVertex;
if (FindFther(edge1) != FindFther(edge2)) {
Union(edge1,edge2);
weights = weights + e.weight;
count++;
}
}
if(count==numbersofvertexs){
break;
}
}
return weights;
}
}
public class Kruskal{
public static void main(String []args){
Scanner cin=new Scanner(System.in);
int vertexs=cin.nextInt();
int edges=cin.nextInt();
DirectedGraph a=new DirectedGraph(edges);
System.out.print(a.kruskal(vertexs));
}
}
四、易错点
1.整型与字符型互转
注意,这个是ASCII码的转化
//整型转字符
int a=65;
char c=(char) a;
//字符转整型
char b='a';
int a=(int )b;
2.字符串转整型,浮点型,双精度型
int i=Integer.parseInt(" ");
double a=Double.paresDouble(" ")
//其他的以此类推
但是有一个字符串转字符的比较特别,要用循环
String a="abc";
for(int i=0;i<a.length();i++){
char c=a.charAt(i);
}
3.整型,浮点型,字符等乱七八糟的转字符串型的
String s=String.valueOf();//通用这个
4.List的自定义排序
List<String> list=new ArrayList<>();
Collections.sort(list,new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
return o1-o2;//升序排序,如果是02-01则是降序
}
});
}
具体请看:
https://www.cnblogs.com/yw0219/p/7222108.html?utm_source=itdadao&utm_medium=referral
5.逐个读取字符串
Scanner cin=new Scanner (System.in);
String s=cin.next();
注意其实,Scanner里面有很多的不同类型的next,要具体情况具体分析
------------------------------------3.27更新-------------------------------------------------------
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
/*
* 最小生成树满足的性质:
* (1)由图产生的边权之和最小的树,因此对于树而言,它的边数等于顶点数减1
* (2)树内不能有环
* (3)Krusal采用贪心的思想,先把边权从小到大排序,然后每次取一条边,判断左右端点是否
* 属于同一个集合(最小生成树集与非最小生成树集),若是则将此边加入最小生成树集,重复这个步骤直到
* 集合内的边数等于顶点数减一,则说明生成完毕。
* 本节的易错点知识:
* (1)超级易错!!!!对于非基本类型的数组,在使用前一定要对每一一个元素实例化,
* 即用一个for循环 edge[i]=new Edge();否则就会报 空指针错误
* (2)!在设置全局变量数组的时候,可以先声明,不实例化具体的数量,等在main函数里面才new相应的数量,以避免空指针的问题,比如Arrays.sort的时候,如果没有限定sort的范围,那么就是全部元素sort,
* 若后面的没有实例化那么就会报空指针错误
* (3)”出现in thread "main" java.lang.NullPointerException “ 这个错误的时候重点检查新建的其他类数组是否有对每个元素实例化
*/
public class _最小生成树kruskal {
static class Edge{
int left;
int right;
int weight;
Edge(){}
}
static int maxn=100;//边数的最大值
static Edge []edge;//一开始只需要声明,不需要实例化数量,在main函数中实例化,否则就会出现空指针问题,在Arrays.sort的时候
static int []father=new int [maxn];//并查集的父亲数组
static Comparator<Edge>cmp=new Comparator <Edge>() {
public int compare(Edge acd,Edge bfc) {
return (acd.weight-bfc.weight);//升序
}
};
static int findfather(int x) {
int a=x;
while(x!=father[x])//一开始的father数组初始化为自己
{
x=father[x];
}
//路径压缩
while(a!=father[a]) {
int z=a;
a=father[a];//走下一步
father[z]=x;//改上一步的父亲
}
return x;
}
static int kruskal(int n,int m)//返回最小生成树的边权重之和,n为顶点数,m为维数
{
int ans=0;int edgenumber=0;
for(int i=0;i<n;i++) {
father[i]=i;//初始化并查集数组
}
Arrays.sort(edge,cmp);
for(int i=0;i<m;i++) {
int a=findfather(edge[i].left);
int b=findfather(edge[i].right);
if(a!=b) {
father[a]=b;
ans=ans+edge[i].weight;
edgenumber++;
if(edgenumber==n-1) {
break;//边数等于顶点数-1,说明找到了一个最小生成树
}
}
}
if(edgenumber==n-1) return ans;
else return -1;//说明没有连通的最小生成树
}
public static void main(String []args) {
Scanner cin=new Scanner (System.in);
int n,m;
n=cin.nextInt();m=cin.nextInt();
edge=new Edge[m];
for(int k=0;k<m;k++) {
edge[k]=new Edge();
}
//其实也可以在这里初始化并查集的,但是选择在Kruskal里面处理了
for(int i=0;i<m;i++) {
edge[i].left=cin.nextInt();
edge[i].right=cin.nextInt();
edge[i].weight=cin.nextInt();
}
int ans=kruskal(n,m);
System.out.println(ans);
}
}