32.克鲁斯卡尔算法

克鲁斯卡尔算法

克鲁斯卡尔算法(kruskal)跟普里姆算法一样,目的都是求无向图的最小生成树。普里姆算法核心在于一个顶点接一个顶点的找出最短路径,克鲁斯卡算法在于将每一条边进行升序排序,然后通过边进行筛选从而组成最小生成树。

实现步骤

核心思想在于按权值从小到大排序选择n-1条边,并保证选择边不会构成回路。用到核心的数据结构并查集来判断是否构成回路。

  1. 将所有的边按权值大小升序排列
  2. 创建一个数组selectEdges存在选择出来的边
  3. 循环遍历已经排好序的边,如果该边不构成回路,则添加到selectEdges

并查集

并查集是一种树型或者链表的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

  1. 初始化数组为的值为索引本身,如果arr[n]=n 说明该点的根节点就是他本身
  2. 并查集核心方法找到一个节点的根节点find 和 union合并2个节点
  3. 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 +
                ">";
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值