- kruskal算法概述:按照边的权重(从小到大)处理它
们,将边加入最小生成树中,加入的边不会与已经加入最小生成树的边构成环,直到树中含有V-1条边为止。
kruskal的核心思想就是默认把图中每个顶点都当做一个树,创建一个队列,保存最小生成树所有的边,同时将该图中所有的边全部存入到一个最小优先队列中,然后依次从队列中弹出最小边,然后判断这个边连接的这两个顶点是否已经处于同一个数中了,如果处于同一个树中了,则跳过本次循环,否则合并这两个顶点到同一个树中,然后将该条边添加至最小生成树的边中。
- 并查集API
package com.suanfa.uf;
//路径压缩:防止在找某个元素的根节点时变成一种线性查找
//如何解决?设q元素的到根节点的路径是4,p节点到根节点的路径是2,那么要让p节点的根节点等于q节点的根节点
//这样每次合并可以保证将高度低的树合并到高度高的树上,不会增加数的高度。
public class UF_Tree_Weighted {
private int[] eleAndGroup;
private int count;//当前并查集中剩余组的个数
private int[] sc;//记录每个根节点对应的树中保存的每个节点的个数
public UF_Tree_Weighted(int n){
this.count = n;
//初始化一个具有n个元素的并查集
eleAndGroup = new int[n];
for (int i = 0; i < eleAndGroup.length; i++) {
eleAndGroup[i] = i;//初始化默认组
}
//默认情况下,每个树中的元素个数都是1
this.sc = new int[n];
for (int i = 0; i < sc.length; i++) {
sc[i] = 1;
}
}
//查找第p个元素所在的组
public int find(int p){
//找到p元素的组,继续找p元素的组的组,如果当前元素的组等于当前元素,则表示找打了
int group = eleAndGroup[p];
while (group != eleAndGroup[group]){
group = eleAndGroup[group];
}
return group;
}
//判断第p个元素和第q个元素是否处于同一个组
public boolean connected(int p,int q){
return find(p) == find(q);
}
//合并p和q这两个元素
public boolean union(int p,int q){
if (connected(p,q)){
return false;
}
//合并两个树,这里注意,要找到其中一个树的根节点,让这个根节点的值等于q节点的根节点
int pRoot = find(p);
int qRoot = find(q);
if (sc[pRoot] < sc[qRoot]){
//qRoot大,将pRoot合并到qRoot中
eleAndGroup[pRoot] = qRoot;
//qRoot的数量等于qRoot数量 + sc[pRoot]
sc[qRoot] = sc[qRoot] + sc[pRoot];
}else {
//pRoot大
eleAndGroup[qRoot] = pRoot;
sc[pRoot] = sc[pRoot] + sc[qRoot];
}
count--;
return true;
}
public int getGroupCount() {
return count;
}
}
- 最小优先队列API
//最小优先堆
public class MinHeap<T extends Comparable<T>> {
private int size;
private T[] elements;
public MinHeap(int capacity) {
this.elements = (T[]) new Comparable[capacity];
}
public int size(){
return size;
}
//添加一个元素,使得父元素的总是小于等于子元素,对子元素的顺序没有要求
public void push(T t) {
elements[++size] = t;
//使用上浮算法,将该元素的移动到合适位置
swim(size);
}
//删除最小的元素
public T deleteMin(){
//交换1与size处的位置
if (size > 0){
exchange(1,size);
T data = elements[size];
elements[size] = null;
size--;
//使用下沉算法,让索引1处的元素移动到指定位置
sink(1);
return data;
}else {
return null;
}
}
private void sink(int position) {
//至少有一个左节点
while (2 * position <= size){
if ((2*position) + 1 <= size){
//说明还有右节点
int minPos = less(2*position,(2*position) + 1) ? 2 * position : (2 * position) + 1;
//比较父节点与较小的那个节点,如果比较小的还要大,则交换位置
if (bigger(position,minPos)){
exchange(position,minPos);
position = minPos;
}else break;
}else {
//只有左节点,比较与左节点的大小,如果比左节点大,则交换位置
if (bigger(position,position * 2)){
exchange(position,position * 2);
position = position * 2;
}else {
break;
}
}
}
}
private boolean bigger(int i, int j) {
return elements[i].compareTo(elements[j]) > 0;
}
private void swim(int size) {
//比较size处的元素与 size/2处的元素,如果size比size / 2位置出元素小,则交换位置
while (size > 1){
if (less(size,size / 2)){
exchange(size,size / 2);
size /= 2;
}else {
break;
}
}
}
private boolean less(int i,int j){
return elements[i].compareTo(elements[j]) < 0;
}
private void exchange(int i, int j) {
T temp = elements[i];
elements[i] = elements[j];
elements[j] = temp;
}
}
- 加权边API
//无向图边
public class Edge implements Comparable<Edge>{
private int v;
private int w;
private double weight;//边的权重
public Edge(int v,int w,double weight){
this.v = v;
this.w = w;
this.weight = weight;
}
public double getWeight() {
return weight;
}
public int either(){
return v;
}
public int other(int v){
if (this.v == v){
return w;
}else {
return this.v;
}
}
@Override
public int compareTo(Edge o) {
double result = this.weight - o.weight;
return result > 0 ? 1 : result == 0 ? 0 : - 1;
}
}
- 加权无向图API
//加权无向图
public class EdgedWeightedGraph {
private int nums;//保存顶点的个数
private int edgeNums;//保存边的个数
private List<Edge>[] edges;
public EdgedWeightedGraph(int num){
this.nums = num;
this.edgeNums = 0;
this.edges = new List[num];
for (int i = 0; i < edges.length; i++) {
edges[i] = new ArrayList<>();
}
}
public boolean addEdge(Edge edge){
int v = edge.either();
int w = edge.other(v);
if (edges[v].contains(w)){
return false;
}
edges[v].add(edge);
edges[w].add(edge);
edgeNums++;
return true;
}
public List<Edge> getEdges(int v){
return edges[v];
}
//获取所有的边
public List<Edge> allEdges(){
List<Edge> allEdges = new ArrayList<>();
for (int i = 0; i < edges.length; i++) {
List<Edge> edge = edges[i];
for (Edge e : edge) {
int w = e.other(i);
//在无向图中,所有顶点是不可能重复的,一条边连接了两个顶点,这两个订单必然是一大一小
// 所以在添加边时,只需判断比other边小,如果true就添加
if (i < w){
allEdges.add(e);
}
}
}
return allEdges;
}
}
- kruskal算法API
public class KruskalMST {
private List<Edge> treeEdges = new ArrayList<>();//保存最小生成树的所有边
private MinHeap<Edge> edgeMinHeap;
private UF_Tree_Weighted ufTree;
public KruskalMST(EdgedWeightedGraph graph){
this.ufTree = new UF_Tree_Weighted(graph.getNums());
edgeMinHeap = new MinHeap<>(graph.getEdgeNums() + 1);
List<Edge> edges = graph.allEdges();//获取所有的边
for (Edge edge : edges) {
edgeMinHeap.push(edge);
}
while (edgeMinHeap.size() >0 && treeEdges.size() < graph.getNums() - 1){
//从最小优先队列中弹出一个边
Edge edge = edgeMinHeap.deleteMin();
int v = edge.either();
int w = edge.other(v);
//判断这两个顶点是否在同一个数中
if (!ufTree.connected(v,w)){
//这两个顶点不在同一个树中
ufTree.union(v,w);
treeEdges.add(edge);
}
}
}
public List<Edge> getEdges() {
return treeEdges;
}
}
测试数据
8
16
4 5 0.35
4 7 0.37
5 7 0.28
0 7 0.16
1 5 0.32
0 4 0.38
2 3 0.17
1 7 0.19
0 2 0.26
1 2 0.36
1 3 0.29
2 7 0.34
6 2 0.40
3 6 0.52
6 0 0.58
6 4 0.93
测试代码
public class kruskalTest {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new FileReader("min_create_tree_test.txt"));
Integer nums = Integer.parseInt(br.readLine());
EdgedWeightedGraph graph = new EdgedWeightedGraph(nums);
int edges = Integer.parseInt(br.readLine());
for (int i = 0; i < edges; i++) {
String s = br.readLine();
String[] s1 = s.split(" ");
Edge edge = new Edge(Integer.parseInt(s1[0]), Integer.parseInt(s1[1]), Double.parseDouble(s1[2]));
graph.addEdge(edge);
}
KruskalMST kruskalMST = new KruskalMST(graph);
for (Edge edge : kruskalMST.getEdges()) {
System.out.println(edge);
}
}
}
最终打印
最小生成树