克鲁斯卡尔算法
克鲁斯卡尔算法(kruskal)跟普里姆算法一样,目的都是求无向图的最小生成树。普里姆算法核心在于一个顶点接一个顶点的找出最短路径,克鲁斯卡算法在于将每一条边进行升序排序,然后通过边进行筛选从而组成最小生成树。
实现步骤
核心思想在于按权值从小到大排序选择n-1条边,并保证选择边不会构成回路。用到核心的数据结构并查集来判断是否构成回路。
- 将所有的边按权值大小升序排列
- 创建一个数组selectEdges存在选择出来的边
- 循环遍历已经排好序的边,如果该边不构成回路,则添加到selectEdges
并查集
并查集是一种树型或者链表的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
- 初始化数组为的值为索引本身,如果arr[n]=n 说明该点的根节点就是他本身
- 并查集核心方法找到一个节点的根节点find 和 union合并2个节点
- union合并方法的时候如果根节点不同,则将一个根节点挂在另一个根节点上集合
/**
* 问题核心在于 并查集的数据结构
* 并查集: 一个长度为顶点个数的数组 初始化数组的默认数组的每一个元素的值等于元素索引本身
* 相当于初始化的时候每个顶点都是独立的父顶点就是自己本身,当某一个顶点的父节点发生变化修改数组的值
* 并查集的合并,如果两个子树需要合并 ,在父节点不同的前提下,将某一个子树的父节点挂在另一个子树的父节点即可
* 判断顶点的终点 采用了并查集的思想 一个顶点的父节点如果相同说明构成回路
* 约定当数组中 下标值等于为自身元素索引的时候为该顶点为终点
* @param parentVertex 保存了顶点的父级信息
* @param index 需要查询的顶点
* @return 返回某一个顶点的终点
*/
public int getRoot(int[] parentVertex, int index) {
while (parentVertex[index] != index) {
index = parentVertex[index];
}
return index;
}
/**
* 并查集合并
* @param parentVertex
* @param start
* @param end
*/
public void union(int[] parentVertex, int start, int end) {
// 先获取两个顶点的顶级父顶点
int startRoot = getRoot(parentVertex, start);
int endRoot = getRoot(parentVertex, end);
// 如果顶级父顶点不同 则将某一个子树父节点挂在另一个子树上即可
if (startRoot != endRoot) {
parentVertex[startRoot] = endRoot;
}
}
克鲁斯卡尔完整代码示例
package com.corn.algorithm.kruskal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.corn.algorithm.kruskal.KruskalDemo.INF;
public class KruskalDemo {
// 使用INF表示两个顶点不连通
public static final int INF = Integer.MAX_VALUE;
public static void main(String[] args) {
char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
//克鲁斯卡尔算法的邻接矩阵
int matrix[][] = {
/*A*//*B*//*C*//*D*//*E*//*F*//*G*/
/*A*/ {0, 12, INF, INF, INF, 16, 14},
/*B*/ {12, 0, 10, INF, INF, 7, INF},
/*C*/ {INF, 10, 0, 3, 5, 6, INF},
/*D*/ {INF, INF, 3, 0, 4, INF, INF},
/*E*/ {INF, INF, 5, 4, 0, 2, 8},
/*F*/ {16, 7, 6, INF, 2, 0, 9},
/*G*/ {14, INF, INF, INF, 8, 9, 0}};
KruskalGraph kruskalGraph = new KruskalGraph(vertexs, matrix);
kruskalGraph.print();
List<Edge> targetEdges = kruskalGraph.kruskal();
System.out.println(targetEdges);
System.out.println("总权值如下:");
// <E,F> <C,D> <D,E> <B,F> <E,G> <A,B>
targetEdges.stream().map(e -> e.weight).reduce(Integer::sum).ifPresent(System.out::println);
}
}
class KruskalGraph {
// 顶点数组
char[] vertexes;
// 邻接矩阵
int[][] edges;
/**
* 将邻接举证封装成Edge对象数组 并且按升序排序
*/
List<Edge> edgesList;
public KruskalGraph(char[] vertex, int[][] edges) {
this.vertexes = vertex;
this.edges = edges;
edgesList = new ArrayList<>();
// 将邻接矩阵转成边集合
for (int i = 0; i < edges.length; i++) {
int[] edge = edges[i];
for (int j = i + 1; j < edge.length; j++) {
if (edges[i][j] != INF) {
edgesList.add(new Edge(vertex[i], vertex[j], edges[i][j]));
}
}
}
}
public void print() {
for (int i = 0; i < vertexes.length; i++) {
for (int j = 0; j < vertexes.length; j++) {
System.out.printf("%12d", edges[i][j]);
}
System.out.println();
}
}
public List<Edge> kruskal() {
// 最终的最小生成树集合
List<Edge> selectEdges = new ArrayList<>();
// 边集合进行排序
Collections.sort(edgesList);
// 定义一个数组来保存每一个顶点的父级信息
int[] parentInfo = new int[vertexes.length];
for (int i = 0; i < parentInfo.length; i++) {
parentInfo[i] = i;
}
for (Edge edge : edgesList) {
// 判断边的2个顶点是否构成回路 如果不构成添加到 目标集合中
int start = getPosition(edge.start);
int end = getPosition(edge.end);
if (getRoot(parentInfo, start) != getRoot(parentInfo, end)) {
// 不构成回路 加入目标集合
selectEdges.add(edge);
// 修改parentInfo信息将当前顶点的父级信息
// parentInfo[start] = end;
union(parentInfo, start, end);
}
}
return selectEdges;
}
/**
* 通过字符获取 顶点索引的位置
*
* @param point
* @return
*/
public int getPosition(char point) {
for (int i = 0; i < vertexes.length; i++) {
char vertex = vertexes[i];
if (point == vertex) return i;
}
return -1;
}
/**
* 问题核心在于 并查集的数据结构
* 并查集: 一个长度为顶点个数的数组 初始化数组的默认数组的每一个元素的值等于元素索引本身
* 相当于初始化的时候每个顶点都是独立的父顶点就是自己本身,当某一个顶点的父节点发生变化修改数组的值
* 并查集的合并,如果两个子树需要合并 ,在父节点不同的前提下,将某一个子树的父节点挂在另一个子树的父节点即可
* 判断顶点的终点 采用了并查集的思想 一个顶点的父节点如果相同说明构成回路
* 约定当数组中 下标值等于为自身元素索引的时候为该顶点为终点
* @param parentVertex 保存了顶点的父级信息
* @param index 需要查询的顶点
* @return 返回某一个顶点的终点
*/
public int getRoot(int[] parentVertex, int index) {
while (parentVertex[index] != index) {
index = parentVertex[index];
}
return index;
}
/**
* 并查集合并
* @param parentVertex
* @param start
* @param end
*/
public void union(int[] parentVertex, int start, int end) {
// 先获取两个顶点的顶级父顶点
int startRoot = getRoot(parentVertex, start);
int endRoot = getRoot(parentVertex, end);
// 如果顶级父顶点不同 则将某一个子树父节点挂在另一个子树上即可
if (startRoot != endRoot) {
parentVertex[startRoot] = endRoot;
}
}
}
//边 实现Comparable接口方便排序
class Edge implements Comparable<Edge> {
// 起点索引
char start;
// 终点索引
char end;
// 权值
int weight;
public Edge(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public int compareTo(Edge o) {
return this.weight - o.weight;
}
@Override
public String toString() {
return "<" + start +
"->" + end +
" weight:" + weight +
">";
}
}