程序员必会十大算法之Kruskal算法

Posted 独饮敌敌畏丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序员必会十大算法之Kruskal算法相关的知识,希望对你有一定的参考价值。

Kruskal算法有两个要求:

①对图的所有边按照权值大小进行排序。
②将边添加到最小生成树中时,怎么样判断是否形成了回路。

①很好解决,采用排序算法进行排序即可。
②处理方式是:记录顶点在"最小生成树"中的终点,顶点的终点是"在最小生成树中与它连通的最大顶点"。然后每次需要将一条边添加到最小生存树时判断该边的两个顶点的终点是否重合,重合的话则会构成回路。

如何判断回路?

将所有顶点按照从小到大的顺序排列好之后,某个顶点的终点就是"与它连通的最大顶点"。我们加入的边的两个顶点不能都指向同一个终点,否则构成回路。
具体是这个方法

/**
 * 返回传入的点的终点的下标
 * @param ends ends[i] = j i是传入的点的下标,j是i的相邻点的下标
 * @param i
 * @return
 */
public static int getEnd(int[] ends,int i){
    //如果这个点有可以到达的点,那么就把可以到达的点赋值到i,然后如果i还有可以到达的点,那么就重复此操作。一直到最后,最后的点就是最初传入的点的终点
    //其实ends数组记的是每一个点的相邻点,而不是终点,但是可以通过ends数组,利用while循环,求出一个点的终点
    while (ends[i] != 0){
        i = ends[i];
    }
    return i;
}

代码

public class Main {
    static char[] data = {'A','B','C','D','E','F','G'};
    public static void main(String[] args) {
        //char[] data = {'A','B','C','D','E','F','G'};
        int[][] weight = {
                {0,12,10000,10000,10000,16,14},
                {12,0,10,10000,10000, 7,10000},
                {10000,10,0,3,5,6,10000},
                {10000,10000,3,0,4,10000,10000},
                {10000,10000,5,4,0,2,8},
                {16,7,6,10000,2,0,9},
                {14,10000,10000,10000,8,9,0}};

        MGraph mGraph = new MGraph(data.length);
        mGraph.createGraph(data,weight);
        mGraph.showGraph();
        createMinTreeKruskal(mGraph);
        //System.out.println(Arrays.toString(MEdge.getEdges(mGraph)));
        //System.out.println(Arrays.toString(MEdge.getSortedEdges(MEdge.getEdges(mGraph))));
    }

    /**
     * 克鲁斯卡尔算法构建最小生成树
     */
    public static void createMinTreeKruskal(MGraph mGraph){
        if (mGraph.vertexNum == 0){
            return;
        }
        //首先得到传入的图的所有边按权值从小到大的顺序排序
        MEdge[] sortedEdges = MEdge.getSortedEdges(MEdge.getEdges(mGraph));//所有边
        ArrayList<MEdge> mEdges = new ArrayList<>();

        //创建所有顶点的终点集
        int[] ends = new int[mGraph.vertexNum];

        for (int i = 0;i < sortedEdges.length;i++){
            int p1 = MEdge.getPointIndex(sortedEdges[i].startPoint,data);
            int p2 = MEdge.getPointIndex(sortedEdges[i].endPoint,data);

            //判断此边的两个点的终点是否相同
            int end1 = MEdge.getEnd(ends,p1);
            int end2 = MEdge.getEnd(ends,p2);

            if (end1 != end2){
                ends[end1] = end2;
                mEdges.add(sortedEdges[i]);
            }
        }

        System.out.println(mEdges);
    }
}
//创建边
class MEdge{
    char startPoint;
    char endPoint;
    int weight;

    public MEdge(char startPoint, char endPoint, int weight) {
        this.startPoint = startPoint;
        this.endPoint = endPoint;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "MEdge{" +
                "startPoint=" + startPoint +
                ", endPoint=" + endPoint +
                ", weight=" + weight +
                '}';
    }

    //传入一个图,根据其邻接矩阵,得到其边的数目
    public static int getEdgesNum(MGraph mGraph){
        if (mGraph.vertexNum == 0){
            return -1;
        }
        int edgeNum = 0;

        for (int i = 0;i < mGraph.vertexNum;i++){
            for(int j = i + 1;j < mGraph.vertexNum;j++){
                if (mGraph.weight[i][j] != 10000){
                    //说明这是一条边,i和j分别是其端点
                    edgeNum++;
                }
            }
        }

        return edgeNum;
    }

    //传入一个图,根据其邻接矩阵,得到其边
    public static MEdge[] getEdges(MGraph mGraph){
        if (mGraph.vertexNum == 0){
            return null;
        }
        MEdge[] mEdges = new MEdge[getEdgesNum(mGraph)];
        int index = 0;

        for (int i = 0;i < mGraph.vertexNum;i++){
            for(int j = i + 1;j < mGraph.vertexNum;j++){
                if (mGraph.weight[i][j] != 10000){
                    //说明这是一条边,i和j分别是其端点
                    mEdges[index] = new MEdge(mGraph.data[i],mGraph.data[j],mGraph.weight[i][j]);
                    index++;//这里一定别忘了+1
                }
            }
        }

        return mEdges;
    }

    //传入一个边集合,根据其权值大小进行排序
    public static MEdge[] getSortedEdges(MEdge[] mEdges){
        for (int i = 0;i < mEdges.length - 1;i++){
            for (int j = 0;j < mEdges.length - i - 1;j++){
                if (mEdges[j + 1].weight < mEdges[j].weight){
                    //不能用下面的 因为下面这种仅仅改变了边的权,我们应该整个边都去改变
//                    int temp = mEdges[j + 1].weight;
//                    mEdges[j + 1].weight = mEdges[j].weight;
//                    mEdges[j].weight = temp;

                    MEdge mEdge = mEdges[j + 1];
                    mEdges[j + 1] = mEdges[j];
                    mEdges[j] = mEdge;
                }
            }
        }

        return mEdges;
    }

    /**
     * 返回传入的点的终点的下标
     * @param ends ends[i] = j i是传入的点的下标,j是i的终点的下标
     * @param i
     * @return
     */
    public static int getEnd(int[] ends,int i){
        //如果这个点有可以到达的点,那么就把可以到达的点赋值到i,然后如果i还有可以到达的点,那么就重复此操作。
        //其实ends数组记的是每一个点的相邻点,而不是终点,但是可以通过ends数组,利用while循环,求出一个点的终点
        while (ends[i] != 0){
            i = ends[i];
        }

        return i;
    }
    //输入一个顶点(char类型),返回其索引值(int类型)
    public static int getPointIndex(char point,char[] datas){
        for (int i = 0;i < datas.length;i++){
            if (datas[i] == point){
                return i;
            }
        }

        return -1;//没找到
    }
}
//这是图
class MGraph{
    //节点数目
    int vertexNum;
    //节点
    char[] data;
    //边的权值
    int[][] weight;

    MGraph(int vertexNum){
        this.vertexNum = vertexNum;
        data = new char[vertexNum];
        weight = new int[vertexNum][vertexNum];
    }

    //图的创建
    public void createGraph(char[] mData,int[][] mWeight){
        if (vertexNum == 0){
            return;//节点数目为0 无法创建
        }

        for (int i = 0;i < data.length;i++){
            data[i] = mData[i];
        }

        for (int i = 0;i < mWeight.length;i++){
            for (int j = 0;j < mWeight.length;j++){
                weight[i][j] = mWeight[i][j];
            }
        }
    }

    //打印图
    public void showGraph(){
        if (vertexNum == 0){
            return;
        }

        for (int[] oneLine: weight){
            for (int oneNum: oneLine){
                System.out.printf("%20d",oneNum);
            }
            System.out.println();
        }
    }
}

结果

                 0                  12               10000               10000               10000                  16                  14
                  12                   0                  10               10000               10000                   7               10000
               10000                  10                   0                   3                   5                   6               10000
               10000               10000                   3                   0                   4               10000               10000
               10000               10000                   5                   4                   0                   2                   8
                  16                   7                   6               10000                   2                   0                   9
                  14               10000               10000               10000                   8                   9                   0
[MEdge{startPoint=E, endPoint=F, weight=2}, MEdge{startPoint=C, endPoint=D, weight=3}, MEdge{startPoint=D, endPoint=E, weight=4}, MEdge{startPoint=B, endPoint=F, weight=7}, MEdge{startPoint=E, endPoint=G, weight=8}, MEdge{startPoint=A, endPoint=B, weight=12}]

以上是关于程序员必会十大算法之Kruskal算法的主要内容,如果未能解决你的问题,请参考以下文章

程序员必会十大算法之弗洛伊德算法

程序员必会十大算法之Prim算法

程序员必会十大算法之骑士周游问题

十大排序算法(程序员必会)

LeetCode代码模板,刷题必会

程序员必会算法之冒泡排序