【程序员必会十大算法】之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}]

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值