- 举个最小生成树的例子:
在一个地区有很多个小村庄,并且每个村之间都可以修路进行连通。实际上,每条路由于地形不一样,所需的经费不同,并且不可能每两个村庄都直接互通,可以间接互通,从而降低经费。如何实现呢?这时候最小生成树的作用就体现出来了。 - 如下图:(每个点代表一个村庄,边上的值代表修路所需经费)
生成最小生成树的方法最经典的有两种:普利姆算法和克鲁斯卡尔算法 。下面我们来说说克鲁斯卡尔算法算法,克鲁斯卡尔算法又叫加边法。 - 通过克鲁斯卡尔算法最小生成树的要点主要简单归纳为如下两点点:
1)找到最短的边
2)判断这两个边上的点能不能连接
a.两个点是新的
b.两个点其中一个是新的
c.连量两个点出自不同的通区域
以C为起点为例:
1)找到最短的边
2)判断是否可以连通: 两个点出自不同的通区域
3)重复第二步
最后将所有点都(直接或者间接)连通成功
上面就是克鲁斯卡尔算法的基本思路。如何用代码实现呢,往下看。
-
代码实现
要记录一个图,我们在代码中就要记录点还有点之间的边,所有我们需要一个点集和一个边集。
点集我们可以通过一个Set集合来充当
边集合我们可以通过一个二维数组来建立: 如下图,表示点与点之间的距离,数组的索引对应MapNode中的index值。
- 封装一个图中所需的点
public class MapNode {
public String val;
public List<MapNode> neighbor;//用来记录最终点周围的连接情况
public int index;//用于绑定图中的边集
public MapNode(String val, int index) {
this.val = val;
this.index = index;
neighbor = new ArrayList<>();
}
}
- 克鲁斯卡尔算法的核心代码
//克鲁斯卡尔算法
public static void kruskal(Set<MapNode> pointSet, int[][] distance){
List<List<MapNode>> rs = new ArrayList<>();//部落的集合
while (!(rs.size() == 1 && rs.get(0).size() == pointSet.size())){
getMinDis(pointSet,distance,rs);
}
}
//找到最短的边并且将两点按条件连接起来
public static void getMinDis(Set<MapNode> pointSet, int[][] distance,List<List<MapNode>> rs){
int minDis = Integer.MAX_VALUE;
MapNode begin = null;
MapNode end = null;
List<MapNode> beginList = null;
List<MapNode> endList = null;
for(int i = 0; i < distance.length; i++){
for(int j = 0; j < distance[i].length; j++){
if(distance[i][j] < minDis && i != j){
MapNode a = getMapNodeByInx(pointSet,i);
MapNode b = getMapNodeByInx(pointSet,j);
List<MapNode> aList = getMapNodeList(rs,a);
List<MapNode> bList = getMapNodeList(rs,b);
if(!(aList != null && bList != null && aList == bList)){
minDis = distance[i][j];
begin = a;
end = b;
beginList = aList;
endList = bList;
}
}
}
}
//循环完后便可找到最短的那天边
begin.neighbor.add(end);
end.neighbor.add(begin);
if(beginList == null && endList == null){//两个都是新的点
List<MapNode> mapNodes = new ArrayList<>();
mapNodes.add(begin);
mapNodes.add(end);
rs.add(mapNodes);
}else if(beginList != null && endList == null){
beginList.add(end);
}else if(beginList == null && endList != null){
endList.add(begin);
}else if(beginList != null && endList != null){
beginList.addAll(endList);
rs.remove(endList);
}
}
//根据索引找到相应的点
public static MapNode getMapNodeByInx(Set<MapNode> pointSet, int index){
for(MapNode node : pointSet)if (node.index == index) return node;
return null;
}
//找到点所在连通区域
public static List<MapNode> getMapNodeList(List<List<MapNode>> rs, MapNode node){
for(List<MapNode> mapNodes : rs)if(mapNodes.contains(node)) return mapNodes;
return null;
}
- 记录上面所举例的图并且通过克鲁斯卡尔算法最终生成最小生成树
public class Main {
public static void main(String[] args) {
//将图中的点构建出来
MapNode pointA = new MapNode("A",0);
MapNode pointB = new MapNode("B",1);
MapNode pointC = new MapNode("C",2);
MapNode pointD = new MapNode("D",3);
MapNode pointE = new MapNode("E",4);
MapNode pointF = new MapNode("F",5);
MapNode pointG = new MapNode("G",6);
int max = Integer.MAX_VALUE;
//图中每个点到图中点的距离(每条边的权值)
int[][] distance = new int[][]{
{0,5,max,13,max,max,8},
{5,0,6,max,max,max,10},
{max,6,0,max,max,8,4},
{13,max,max,0,9,max,11},
{max,max,max,9,0,9,7},
{max,max,8,max,9,0,20},
{8,10,4,11,7,20,0}
};
Set<MapNode> pointSet = new HashSet<>();
pointSet.add(pointA);
pointSet.add(pointB);
pointSet.add(pointC);
pointSet.add(pointD);
pointSet.add(pointE);
pointSet.add(pointF);
pointSet.add(pointG);
kruskal(pointSet,distance);
System.out.println("生成完毕");//debug查看结果
}
- 生成结果:
暂且不全部展开,可以看到,和我们上面生成的最小生成树结果是一样的。