第十四章:常用的十大算法

14.7克鲁斯卡尔算法(Kruskal)

Kruskal算法也是求最短路径(最小生成树)的经典算法,一定要掌握,因为在很多的面试题中都会出现

Kruskal算法介绍:
1、 克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法
2、 基本思想:按照权值从小到大的顺序选择 n-1 条边,并保证这 n-1 条边不构成回路(注意对比,Prim算法是添加顶点,再从顶点选最小的边,Kruskal是直接选最小的边加入)
3、 具体做法:首先构造一个只含 n 个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森 林中不产生回路,直至森林变成一棵树为止

Kruskal算法实践:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Kruskal算法分析:
在这里插入图片描述

Kruskal算法如何判断构成了回路?
在这里插入图片描述
在这里插入图片描述

package com.atguigu20.kruskal;

import java.util.Arrays;

/**
 * @author peng
 * @date 2021/12/6 - 20:17
 *
 * 使用Kruskal算法求最小生成树
 */
public class KruskalCase {
    private int edgeNum;//边的个数
    private char[] vertexs;//顶点数组
    private int[][] matrix;//邻接矩阵
    private 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}
        };

        KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);
        kruskalCase.showKruskalCase();
        Edata[] edges = kruskalCase.getEdges();
        kruskalCase.kruskal();
    }

    /**
     * 初始化
     */
    public KruskalCase(char[] vertexs, int[][] matrix) {
        this.vertexs = vertexs;
        this.matrix = matrix;

        //获得边的数量
        for (int i = 0; i < matrix.length; i++) {
            for (int j = i + 1; j < matrix[0].length; j++) {
                if (matrix[i][j] != INF) {
                    edgeNum++;
                }
            }
        }
    }

    /**
     * 打印二维数组
     */
    public void showKruskalCase() {
        for (int[] row : matrix) {
            System.out.println(Arrays.toString(row));
        }
    }

    /**
     * 对边进行一个大小排序
     */
    public void sortEdges(Edata[] edges) {
        //使用优化版的冒泡排序对边的大小进行排序
        boolean flag = false;
        for (int i = 0; i < edges.length - 1; i++) {
            for (int j = 0; j < edges.length - 1 - i; j++) {
                if (edges[j].weight> edges[j + 1].weight) {
                    int temp = edges[j].weight;
                    edges[j].weight = edges[j + 1].weight;
                    edges[j + 1].weight = temp;
                    flag = true;
                }
            }
            if (!flag) {
                break;
            }
            flag = false;
        }
    }

    /**
     * 输入一点顶点:返回该顶点对应的下标,如果找不到就返回-1
     * @param c
     * @return
     */
    public int getPosition(char c) {
        for (int i = 0; i < vertexs.length; i++) {
            if (c == vertexs[i]) {
                return i;
            }
        }
        return  -1;
    }

    /**
     * 定义一个方法:返回存放边的数组{[A,B,12],[C,D,10],...}
     */
    public Edata[] getEdges() {
        Edata[] edatas = new Edata[edgeNum];//定义一个数组对象,用于保存每一条边的数据,等下可以通过遍历这个数组,实现对边的遍历
        //给数组补充边的信息
        int index = 0;//edatas数组开始的索引
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = i + 1; j < vertexs.length; j++) {
                if (matrix[i][j] != INF) {
                    edatas[index++] = new Edata(vertexs[i],vertexs[j],matrix[i][j]);
                }
            }
        }
        return edatas;
    }

    /**
     * 该方法是获取一i节点为起点的对应的终点的索引位置,由此来判断是否形成了回路,即是判断能不能添加当前的边
     * @param ends:ends数组保存的是,在遍历的过程中每个顶点对应的终点,是实时变化的
     * @param i:表示传入的顶点,对应的下标
     * @return:返回的是顶点为i的对应的终点的下标
     */
    public int getEnd(int[] ends, int i) {
        //返回当前结点对应的终点
        while (ends[i] != 0) {
            //如果当前结点还没有终点,自己就是自己的终点
            i = ends[i];
        }
        return i;
    }

    /**
     * 实现Kruskal算法
     */
    public void kruskal() {
        int index = 0;//最终结果的索引
        int[] ends = new int[edgeNum];//创建一个数组用于保存没有顶点所对应的终点
        //最终路的数量是村庄数量 - 1
        Edata[] res = new Edata[vertexs.length - 1];//创建边的数组用于保存,最后有哪些边会添加到数组中去,也就是保存最后的结果
        //第一步:获取图中的所有的边
        Edata[] edges = getEdges();
        //第二步:将所有边从小到大进行排序
        sortEdges(edges);
        //第三步:如果要添加一条边,判断添加该边之后会不会形成回路?
        for (int i = 0; i < edgeNum; i++) {
            int p1 = getPosition(edges[i].start);//这是第一条边的一个点,假设是A,p1=0
            int p2 = getPosition(edges[i].end);//这是第一条边的另一点,假设是B,p2=2
            int e1 = getEnd(ends, p1);//这是p1对应的那个顶点,e1=0
            int e2 = getEnd(ends, p2);//这是p2对应的那一个顶点,e2=1
            //接下来就要判断,p1、p2对应的顶点是不是同一个点,如果他们的终点都是同一个点的话,说明添加这条边之后会形成回路
            if (e1 != e2) {
                //如果他们的顶点是不同的,说明添加这条边之后不会形成回路
                ends[e1] = e2;//添加这条边之后,将p1的终点改为p2的终点,e1=1,表示A的终点是B
                res[index++] = edges[i];//将当前这条边加入到最终的结果
            }
        }

        //在这里就获得了最小的生成树,输出最小生成树
        for (int i = 0; i < res.length; i++) {
            System.out.println(res[i]);
        }
    }
}

/**
 * 创建一个边类,用于保存边的信息
 */
class Edata{
    char start;//边的一个点
    char end;//边的另一个点
    int weight;//边的权重

    public Edata(char start, char end, int weight) {
        this.start = start;
        this.end = end;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Edata{" +
                "start=" + start +
                ", end=" + end +
                ", weight=" + weight +
                '}';
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值