普利姆与克鲁斯卡尔算法思路记录与对比

本文介绍了如何使用克鲁姆算法从指定点出发构造最短生成树,以及克鲁斯卡尔算法在公交站问题中的应用,通过邻接矩阵和访问数组判断是否构成回路,确保找到n-1条不构成环路的最短路线。
摘要由CSDN通过智能技术生成

修路问题:构成最短生成树,从指定的一个点,寻找n-1条路线构成最短的路径,且不能构成回路。使用克鲁姆算法

例如指定A开始,当前最短的是2,所以修AG

A和G顶点附近最短的路径是GB,3,所以修AGB

A和G和B顶点附近最短的路径是GE,5,所以修AGBE

到了目前,AGBE四个顶点附附近最短的路径是AB和EF,但是因为连接了AB会构成ABG回路(A和B已经被访问过),因此选择EF。

后面都是上面的这种规则,直到构成n-1条边。

思路:

①先创造一个是否访问的数组visited,然后将指定的点设为已访问

②第一个循环,从1开始,因为只需要找n-1条边。

③第二个循环,寻找可能已经访问的点

④第三个循环,寻找已经访问的点到没访问的点最短的值设定为max,且记录行列

ps:不构成回路的判断,因为一个是访问,一个是为未访问的,如果两个点都是已访问,再连接才会构成回路

每一次第一层的寻找后,就可以得到当前已访问的点到其他点最短的距离

代码:

代码中使用了邻接矩阵来存储图,并且提供了两个类:MGraph 和 MinTree。

MGraph 类包括三个成员变量:节点个数 verxs、节点数据 data、邻接矩阵 weight。构造函数 MGraph(int verxs) 中初始化了这些变量。

MinTree 类包括三个方法:构造函数 MinTree(MGraph graph, int verxs, char data[], int[][] weight)、showGraph(MGraph graph) 和 prim(MGraph mGraph, int v)。

构造函数 MinTree(MGraph graph, int verxs, char data[], int[][] weight) 用于创建图的邻接矩阵,将节点数据和邻接矩阵存入 MGraph 对象中。

showGraph(MGraph graph) 方法用于展示邻接矩阵。

prim(MGraph mGraph, int v) 方法是 Prim 算法的核心实现。它从指定的节点开始,逐步构造最小生成树。首先将指定节点标记为已访问,然后在未被访问过的节点中查找到已访问节点距离最近的节点。找到之后将该节点标记为已访问,然后打印出这条边的信息。重复以上步骤,直到构造完成最小生成树。

需要注意的是,Prim 算法构造最小生成树的时候,只用找 n-1 条边即可,因为最小生成树中有 n 个节点,n-1 条边。

package ysh.algorithm;

import java.util.Arrays;

public class PrimAlgorithm {
    public static void main(String[] args) {
        char[] data = new char[]{'A','B','C','D','E','F','G'};
        int verxs = data.length;
        //邻接矩阵的关系使用二维数组表示,10000 这个大数,表示两个点不联通
        int [][]weight=new int[][]{
                {10000,5,7,10000,10000,10000,2}, {5,10000,10000,9,10000,10000,3}, {7,10000,10000,10000,8,10000,10000}, {10000,9,10000,10000,10000,4,10000}, {10000,10000,8,10000,10000,5,4}, {10000,10000,10000,4,5,10000,6}, {2,3,10000,10000,4,6,10000},};
        MGraph mGraph = new MGraph(verxs);
        MinTree minTree = new MinTree(mGraph, verxs, data, weight);//构造最小生成树
        minTree.showGraph(mGraph);//展示
//        普利姆算法,从B节点开始
        minTree.prim(mGraph, 1);
    }


}
class MGraph{
    int verxs;//表示图的节点个数
    char[] data;//节点数据
    int[][] weight;//存放边,临接矩阵

    public MGraph(int verxs) {
        this.verxs = verxs;
        data=new char[verxs];
        weight=new int[verxs][verxs];
    }
}
class MinTree{

//创建图的邻接矩阵
    /**
     *
     * @param graph 图对象
     * @param verxs 图对应的顶点个数
     * @param data 图的各个顶点的值
     * @param weight 图的邻接矩阵
     */
    public  MinTree(MGraph graph, int verxs, char data[], int[][] weight) {
        int i, j;
        for(i = 0; i < verxs; i++) {//顶点
            graph.data[i] = data[i];
            for(j = 0; j < verxs; j++) {
                graph.weight[i][j] = weight[i][j];
            }
        }
    }
    //显示图的邻接矩阵
    public void showGraph(MGraph graph) {
       for(int[] link: graph.weight) {
            System.out.println(Arrays.toString(link));
        }
    }

    /**
     *
     * @param mGraph  //地图
     * @param v    //从哪个节点开始
     */
    public void prim(MGraph mGraph, int v) {
        int[] visited = new int[mGraph.verxs];
        visited[v]=1;
        int row=-1;
        int column=-1;
        int max=10000;
        for(int i=1;i<mGraph.verxs;i++){//只用找n-1条边即可
            for(int j=0;j<mGraph.verxs;j++){//遍历可能已经访问的点
                for(int k=0;k<mGraph.verxs;k++){//遍历得到可能已经访问的点,其到其他点的距离,取最小去访问
                    if(visited[j]==1&&visited[k]==0&&max>mGraph.weight[j][k]){//得到最小值的判断,且不构成回路,因为一个是访问,一个是为未访问的,如果两个点都是已访问,再连接才会构成回路
                        max=mGraph.weight[j][k];
                        row=j;
                        column=k;
                    }
                }
            }
            visited[column]=1;
            System.out.println(mGraph.data[row]+"  =>  "+mGraph.data[column]+"权值为"+mGraph.weight[row][column]);
            max=10000;
        }
    }
}

公交站问题:构成最短生成树,升序每段权值构成森林,每次提取森林里最短的路径,且不能构成回路。使用克鲁斯卡尔算法

首先找到最短的路径,也就是AG

然后找到BG

然后找到DF和EG,不过D比E前,所以选DF

然后可以找到EG

然后可以找到AB或者EF,但是如果选择AB则构成AGB回路(A和B的终点都是G),所以选择EF

整体思路就是利用克鲁斯卡尔算法,按照边的权重从小到大选择边,直到选择了 n-1 条边为止,构建最小生成树。

思路:

①定义了一个 Data 类来表示边,包括起始顶点、终止顶点和权重,并重写了 toString 方法用于方便打印输出。

②使用getEdges方法得到边的数组。

③使用sortEdges将②的数组进行权值的升序排序

④构造ends数组,里面存放x索引对应的字符,其终点的索引,例如A->C,则ends[0]=2。

getEnd方法传入ends数组和i,需要寻找i的终点,如果是0则表明这条线只有一个点,他的终点是他自己

⑤循环数组里的每个边,得到里面的起始节点和结束节点。判断他们的终点是否相同,相同则构成回路,否则就没有构成回路,然后为end[起点索引]=终点索引。

代码:

  1. 定义了一个 KruskalCase 类,其中包括顶点数组 vertexs、邻接矩阵 matrix、边的个数 edgeNum 等属性,以及构造方法和一些操作方法。
  2. 在构造方法中,通过传入的顶点数组和邻接矩阵计算边的个数,并进行一些初始化操作。
  3. print 方法用于打印邻接矩阵。
  4. kruskal 方法实现了克鲁斯卡尔算法的核心逻辑,包括对边进行排序、寻找终点是否相同、判断是否产生回路等操作。
  5. sortEdges 方法用于对边进行排序。
  6. getPosition 方法用于通过字符找到其在顶点数组中的索引。
  7. getEdges 方法用于从邻接矩阵中获取边的数组。
  8. getEnd 方法用于得到索引为 i 的终点索引,通过不同的 i 返回的索引是否相同来判断是否产生回路。

package ysh.algorithm;

import java.lang.reflect.Array;
import java.util.Arrays;

public class KruskalCase {
    private int edgeNum; //边的个数
    private char[] vertexs; //顶点数组
    private int[][] matrix; //邻接矩阵
    private static final int max = 65536;

    public KruskalCase(char[] vertexs, int[][] matrix) {
       int len=vertexs.length;
       this.vertexs=vertexs.clone();
       this.matrix=matrix.clone();
        for(int i =0; i < len; i++) {
            for(int j = i+1; j < len; j++) {
                if(this.matrix[i][j] != max) {
                    edgeNum++;
                }
            }
        }

    }
    public void print(){
        for(int[] x:matrix){
            System.out.println(Arrays.toString(x));
        }
    }

    public static void main(String[] args) {
        char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int matrix[][] = {
                /*A*/ {0, 12, max, max, max, 16, 14},
                /*B*/ {12, 0, 10, max, max, 7, max},
                /*C*/ {max, 10, 0, 3, 5, 6, max},
                /*D*/ {max, max, 3, 0, 4, max, max},
                /*E*/ {max, max, 5, 4, 0, 2, 8},
                /*F*/ {16, 7, 6, max, 2, 0, 9},
                /*G*/ {14, max, max, max, 8, 9, 0}
        };
        KruskalCase kruskalCase = new KruskalCase(vertexs, matrix);
        System.out.println(kruskalCase.edgeNum);
        kruskalCase.print();
        kruskalCase.kruskal();
    }

    private void kruskal() {
        int index=0;
        int[] ends=new int[edgeNum];//寻找终点是否相同的变量
        Data[] result=new Data[edgeNum];//最后的结果边
        Data[] edges=getEdges();
        sortEdges(edges);
        System.out.println(Arrays.toString(edges));
        for(int i=0;i<edgeNum;i++){
            int one=getPosition(edges[i].start);
            int two=getPosition(edges[i].end);
            int m=getEnd(ends,one);
            int n=getEnd(ends,two);
            if(m!=n){//没有回路
                ends[m]=n;
                result[index++]=edges[i];
            }
        }
        System.out.println("最小生成树为");
        for(int i = 0; i < index; i++) {
            System.out.println(result[i]);
        }

    }
    //排序边
    private void sortEdges(Data[] edges){

        for(int i=0;i<edges.length-1;i++){
            for(int j=0;j<edges.length-1;j++){
                if(edges[j].weight>edges[j+1].weight){
                    Data temp=edges[j];
                    edges[j]=edges[j+1];
                    edges[j+1]=temp;
                }
            }
        }
    }
    //通过字符找其在vertex的索引
    private int getPosition(char ch) {
        for(int i = 0; i < vertexs.length; i++) {
            if(vertexs[i] == ch) {//找到
                return i;
            }
        }
//找不到,返回-1
        return -1;
    }
    //通过matrix与edgenum得到边的数组
    private Data[] getEdges() {
        int index = 0;
        Data[] edges = new Data[edgeNum];
        for(int i = 0; i < vertexs.length; i++) {
            for(int j=i+1; j <vertexs.length; j++) {
                if(matrix[i][j] != max) {
                    edges[index++] = new Data(vertexs[i], vertexs[j], matrix[i][j]);
                }
            }
        }
        return edges;
    }

    /**
     * 得到索引未i的终点索引,通过不同i返回的索引是否相同,判断是否产生回路
     * @param ends
     * @param i
     * @return
     */
    private int getEnd(int[] ends,int i){
        //可能①:当前i没有终点则直接返回i,表示i的终点就是他自己的索引,
        // 可能②:(例如有a->b->c->d的线)当前i有终点则将i赋值为终点b,再寻找终点b是否有终点c,直到找到最终终点d的索引
        while(ends[i]!=0){
            i=ends[i];
        }
        return i;
    }
}
class Data{
    char start;
    char end;
    int weight;

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

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

对比:

普利姆和克鲁斯卡尔问题的区别就是前者是指定从某个点开始构成n-1条线,后者是构成森林然后寻找森林里最短的路径构成n-1条线

  • 48
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值