最近复习到了数据结构中图的遍历和最小生成树部分,我在这里记录用java写的Kruskal算法,方便自己回顾,并且与他人交流学习。
算法简介
和普里姆算法类似都是基于贪心策略并且用来求最小生成树的算法与之不同的是普里姆算法中图是由邻接矩阵构造而成的,算是顶点构造。克鲁斯卡尔算法使用的却是图的另一种数据结构-边集。
算法思想
克鲁斯卡尔的算法的思想相对简单
1.将边由权值从小到大排列,并且记录下来起点,终点,还有权重。
2.按照步骤1中排列好的顺序,将边加入图中,不过有一个条件就是不能构成回路(因为我们每次加入的点都是边中权值最小的,不难理解为什么可以找到最小生成树)
算法的核心是,我们应该怎样去判断加入的边已经构成回环了呢??
解决这个问题的方法是这样的,首先我们可以去去了解一下并查集的概念,https://blog.csdn.net/w1085541827/article/details/52076481。这里所使用的解决方法和并查集的类似,就是是找根节点,我们首先构造一个数组parent,parent[1] = 2,表示v1 和 v2有连通,并且v1的根节点为v2。我们自己写一个find函数,也就是来不断的找一个顶点的根节点,如果一条边找到的起始点和结束点找到的根节点都是一样的点的话,那么就成回路了。如果不一样我们把边画入图中,并且在parent中建立连接关系。
举个例子:就像下面的题目一样,通过代码我们得出结果 ,第8条边有回环,此时的关系应该是这样的我只连接了三个边,剩下的忘记连接了:此时我们所话出来的树应该是这样的我们不断找v5的根节点发现找到的是v6,而v6却并没有根节点,那就他自己就是v6了,都找到了v6所以构成回路,这条边就不适用了。具体的步骤还是看下述代码。
可以通过以下例题来深入理解
代码实例
/**
*
* 使用克鲁斯卡尔算法来实现图的最小生成树
*
* 克鲁斯卡尔锁使用的时边集与普里姆算法的顶点集不同,因此不能使用临接矩阵来表示图
*
* 算法思想:1.先将边按照 权值大小 从小到大排列 记录下来每条边的起始顶点 结束顶点
* 2.将每条边按照权值由小到大的顺序逐步加入到图中
* 3。如果发现加入的边与之前加入的边形成回路,将此边删除
*
*
* @author 65481
*
*/
public class KruskalGraph {
//数组用来存放已经加入到图中并且没有形成回路的边,edgesize为边的个数
private Edge[] edges;
private int edgeSize;
//构造函数
public KruskalGraph(int edgeSize) {
this.edgeSize = edgeSize;
edges = new Edge[edgeSize];
}
//构造图的方法
public void createEdgeArray() {
Edge edge0 = new Edge(4,7 ,7 );
Edge edge1 = new Edge(2,8 ,8 );
Edge edge2 = new Edge(0,1 ,10 );
Edge edge3 = new Edge(0,5 ,11 );
Edge edge4 = new Edge(1,8 ,12 );
Edge edge5 = new Edge(3,7 ,16 );
Edge edge6 = new Edge(1,6 ,16 );
Edge edge7 = new Edge(5,6 ,17 );
Edge edge8 = new Edge(1,2 ,18 );
Edge edge9 = new Edge(6,7 ,19 );
Edge edge10 = new Edge(3,4 ,20 );
Edge edge11 = new Edge(3,8 ,21 );
Edge edge12 = new Edge(2,3 ,22 );
Edge edge13 = new Edge(3,6 ,24 );
Edge edge14 = new Edge(4,5 ,26 );
edges[0] = edge0;
edges[1] = edge1;
edges[2] = edge2;
edges[3] = edge3;
edges[4] = edge4;
edges[5] = edge5;
edges[6] = edge6;
edges[7] = edge7;
edges[8] = edge8;
edges[9] = edge9;
edges[10] = edge10;
edges[11] = edge11;
edges[12] = edge12;
edges[13] = edge13;
edges[14] = edge14;
}
/**
* 克鲁斯卡尔算法的实现
* 核心思想: 创建一个parent数组 ,该数组作用是表示两点的连接关系 比如 parent[1] = 5,表示顶点1和5是有关系的
* @author 65481
*
*/
public void miniSpanTreeKruskal() {
int n,m,sum = 0;
int[] parent = new int [edgeSize];
//初始化数组
for (int i = 0;i < edgeSize ;i ++) {
parent[i] = 0;
}
//算法核心部分
for(int i = 0;i < edgeSize;i ++) {
n = find(parent,edges[i].begin);
m = find(parent,edges[i].end);
if(n != m) {
parent[n] = m;
System.out.println("起始顶点"+edges[i].begin+" 结束顶点"+edges[i].end+ " 权值为"+edges[i].weight);
sum += edges[i].weight;
}else {
System.out.println("在"+i+"条边回环了");
}
}
System.out.println("总权值"+sum);
}
/**
* find 方法是从起始点找一条通路的结束点
*
* @return
*/
public int find(int parent[],int f) {
//当还是通路时
while(parent[f] > 0) {
f = parent[f];
}
return f;
}
public static void main(String[] args) {
KruskalGraph kruskalGraph = new KruskalGraph(15);
kruskalGraph.createEdgeArray();
kruskalGraph.miniSpanTreeKruskal();
}
class Edge{
//一条边所包含的 起始点 结束点 权值
private int begin;
private int end;
private int weight;
//构造函数
public Edge(int begin,int end,int weight) {
this.begin = begin;
this.end = end;
this.weight = weight;
}
//set ,get
public int getBegin() {
return begin;
}
public void setBegin(int begin) {
this.begin = begin;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
}
代码结果
4 7 起始顶点4 结束顶点7 权值为7
2 8 起始顶点2 结束顶点8 权值为8
0 1 起始顶点0 结束顶点1 权值为10
1 5 起始顶点0 结束顶点5 权值为11
5 8 起始顶点1 结束顶点8 权值为12
3 7 起始顶点3 结束顶点7 权值为16
8 6 起始顶点1 结束顶点6 权值为16
6 在8条边回环了
6 在9条边回环了
6 7 起始顶点6 结束顶点7 权值为19
7 在11条边回环了
7 在12条边回环了
7 在13条边回环了
7 在14条边回环了
7 在15条边回环了
总权值99
帮助理解
有关于思路在注释中也有说到,算法的核心部分拿笔写一写一部部计算,还是比较好理解的,举个小例子如下图