大话数据结构-迪杰斯特拉算法(Dijkstra)和弗洛伊德算法(Floyd)

6 最短路径

  最短路径,对于图来说,是两顶点之间经过的边数最少的路径;对于网来说,是指两顶点之间经过的边上权值之和最小的路径。路径上第一个顶点为源点,最后一个顶点是终点。

6.1 迪杰斯特拉(Dijkstra)算法

  以如下无向图为例:

image.png

  我们来计算下标为0的顶点,到其他顶点的最短路径,首先定义三个数组,calculated[j]表示0->j的最短路径是否已计算出来,pathVal[j]表示0->j的最短路径,目前还未进行计算,我们设置所有元素值均为无穷大,pre[j]表示0->j中经历的顶点,例如pre[1]=3、pre[3]=2、pre[2]=0,表示的:

  (1) pre[1]=3表示0->1需要经过3;

  (2) pre[3]=2表示0->3需要经过2;

  (3) pre[2]=0表示0->2可以直达;

  (4) 因此可以得出结论,0->1的路径是:0->2->3->1;

  再定义一个变量min,表示“0->上一个顶点的最小权值”,k表示min对应的顶点的下标,因此初始情况如下所示:

image.png

  接下来来看0到第0个顶点的最短路径,这个不需要计算,肯定是0,且不需要经过其他顶点,因此有如下情况:

image.png

  到目前为止,min仍然为0,k仍然为0,接下来看0到其他顶点的最短路径,我们把arc[0][j]与现有的pathVal[j]比较,规则是:

  (1) calculated[j]=1表示已找到最短路径,不进行任何处理;

  (2) 如果arc[0][j]+min<pathVal[j],则令pathVal[j]=arc[0][j]+min,令pre[j]=k,表示0->j至少要经过顶点k;

  (3) 如果arc[0][j]+min>=pathVal[j],则pathVal[j]不变,pre[j]也不变,表示0->j不会经过顶点k;

  那么,有以下结果:

  (1) calculated[0]=1,不进行任何处理;

  (2) arc[0][1]+min=16+0<pathVal[1]=无穷大,令pathVal[1]=arc[0][1]+min=16,令pre[j]=k=0;

  (3) arc[0][2]、arc[0][3]与arc[0][1]处理方式一致;

  (4) arc[0][4]和arc[0][5]都为无穷大,加上min后等于pathVal[4]和pathVal[5],因此pathVal[4]、pathVal[5]和pre[4]、pre[5]不进行处理;

  (5) 这时来看最新的pathVal[j],发现最小的权是pathVal[3],于是令min=pathVal[3]=12,令k=3,这也表示0->3的最短路径已找到,因此令calculated[3]=1,如下:

image.png

  k=3,即其他未计算出的最短路径,都有可能会经过顶点3,因此我们把arc[3][j]与现有pathVal[j]比较,规则仍与arc[0][j]时一致,特别的,arc[3][2]+min=10+12=26>pathVal[2]=15,因此pathVal[2]和pre[2]不进行变更,当然,arc[3][1]也是同样的:

image.png

  这时,min=15,k=2,因此处理arc[2][j],如下:

image.png

  然后继续处理,直到calculated[j]的元素都为1:

image.png

  我们来看现在的pathVal[j]和pre[j]:

image.png

  可以知道以下内容:

  (1) 顶点5,即E,对应的pathVal[5]是43,pre[4]是4,因此顶点0->5,即C->E的最短路径是43,且中间肯定会经过顶点4即顶点F,即肯定有C->F->E;

  (2) 顶点4,即F,对应的pathVal[4]是26,pre[4]是3,因此顶点0->4,即C->F的最短路径是26,且中间肯定会经过顶点3即顶点B,即肯定有C->B->F,结点(1),那么肯定有C->B->F->E;

  (3) 顶点3,即B,对应的pathVal[3]是12,pre[3]是0,因此顶点0->3,即C->B的最短路径是12,且无中间顶点,那么,结点(1)和(2),得到结论;

    1) C到B的最短路径是12,路径是C->B;

    2) C到F的最短路径是26,路径是C->B->F;

    3) C到E的最短路径是43,路径是C->B->F->E;

  (4) 同样的规则,能分析到:

    1) C到A的最短路径是15,路径是C->A;

    2) C到D的最短路径是15,路径是C->A->D;

  以上即为迪杰斯特拉算法。

  由上也可知,邻接矩阵的迪杰斯特拉算法实现代码如下所示:

/**
 * 迪杰斯特拉算法获取最短路径
 *
 * @param fromVertexIndex 起始顶点下标
 * @return 起始顶点到各顶点的最短路径及前驱顶点列表
 * @author Korbin
 * @date 2023-02-20 14:54:35
 **/
@SuppressWarnings("unchecked")
public Object[][] shortestPathDijkstra(int fromVertexIndex) {

    // 最短路径权值,初始化为fromVertexIndex的权
    W[] pathVal = (W[]) Array.newInstance(infinity.getClass(), vertexNum);

    // 是否已计算,初始值为false
    boolean[] calculated = new boolean[vertexNum];

    // 要到达此顶点应先从哪个顶点过来,初始值为fromVertexIndex
    Integer[] pre = new Integer[vertexNum];
    for (int i = 0; i < vertexNum; i++) {
        pathVal[i] = arc[fromVertexIndex][i];
        pre[i] = fromVertexIndex;
    }

    // 最小权值
    W minWeight = null;
    // 最小权值对应的索引下标
    int minWeightIndex = fromVertexIndex;

    // 已计算的顶点数量,初始值为0
    int calculatedNum;

    // 从fromVertexIndex到fromVertexIndex,令pathVal[fromVertexIndex]为0,令pre[fromVertexIndex]为fromVertexIndex
    // ,令calculated[fromVertexIndex]为true
    pathVal[fromVertexIndex] = (W) Integer.valueOf(0);
    pre[fromVertexIndex] = fromVertexIndex;
    calculated[fromVertexIndex] = true;
    calculatedNum = 1;

    while (calculatedNum < vertexNum) {
        // 循环到所有顶点都计算完毕

        // 处理arc[minWeightIndex][j]
        for (int i = 0; i < vertexNum; i++) {

            // 跳过calculated[j]为true的顶点
            if (!calculated[i]) {
                // 与现有pathVal[j]比较
                if (infinity instanceof Integer) {
                    // 对于arc[minWeightIndex][i]为无穷大的也不处理
                    if (!arc[minWeightIndex][i].equals(infinity)) {
                        // 只处理权值是Integer的情况,其他类型的类似处理
                        if (null == minWeight) {
                            minWeight = (W) Integer.valueOf(0);
                        }
                        W arcI = (W) (Integer.valueOf((Integer) arc[minWeightIndex][i] + (Integer) minWeight));
                        if (arcI.compareTo(pathVal[i]) < 0) {
                            // 令pathVal[j]=arc[minWeightIndex][j] + minWeight
                            pathVal[i] = arcI;
                            // 令pre[j]=minWeightIndex
                            pre[i] = minWeightIndex;
                        }
                    }
                }
            }
        }

        // 从pathVal和pre中取出新的minWeight和minWeightIndex
        minWeight = null;
        for (int i = 0; i < vertexNum; i++) {
            // 跳过已计算最短路径的顶点
            if (!calculated[i] && (null == minWeight || minWeight.compareTo(pathVal[i]) > 0)) {
                minWeight = pathVal[i];
                minWeightIndex = i;
            }
        }

        // 设置minWeightIndex对应的顶点为已访问
        calculated[minWeightIndex] = true;
        calculatedNum++;

        // 用于测试
        System.out.println("min weight is " + minWeight + ", min weight index is " + minWeightIndex);
        StringBuilder builder1 = new StringBuilder("path val is [");
        StringBuilder builder2 = new StringBuilder("pre vertex is [");
        StringBuilder builder3 = new StringBuilder("calculated is [");
        for (int i = 0; i < vertexNum; i++) {
            builder1.append(pathVal[i]).append(",");
            builder2.append(pre[i]).append(",");
            builder3.append(calculated[i]).append(",");
        }
        builder1.append("]");
        builder2.append("]\n\r");
        builder3.append("]");
        System.out.println(builder3);
        System.out.println(builder1);
        System.out.println(builder2);
    }

    Object[][] result = new Object[2][];
    result[0] = pathVal;
    result[1] = pre;
    return result;
}

  邻接表代码如下:

/**
 * 迪杰斯特拉算法获取最短路径
 *
 * @param fromVertexIndex 起始顶点下标
 * @return 起始顶点到各顶点的最短路径及前驱顶点列表
 * @author Korbin
 * @date 2023-02-20 14:54:35
 **/
@SuppressWarnings("unchecked")
public Object[][] shortestPathDijkstra(int fromVertexIndex) {

    // 最短路径权值,初始化为fromVertexIndex的权
    W[] pathVal = (W[]) Array.newInstance(infinity.getClass(), vertexNum);

    // 是否已计算,初始值为false
    boolean[] calculated = new boolean[vertexNum];

    // 要到达此顶点应先从哪个顶点过来,初始值为fromVertexIndex
    Integer[] pre = new Integer[vertexNum];
    for (int i = 0; i < vertexNum; i++) {
        pre[i] = fromVertexIndex;
        pathVal[i] = infinity;
    }

    // 处理第一个顶点
    VertexNode<T, W> vertexNode = vertexes[fromVertexIndex];
    EdgeNode<W> edgeNode = vertexNode.getFirstEdge();
    while (null != edgeNode) {
        int refIndex = edgeNode.getIndex();
        W weight = edgeNode.getWeight();

        pathVal[refIndex] = weight;

        edgeNode = edgeNode.getNext();
    }

    // 最小权值
    W minWeight = null;
    // 最小权值对应的索引下标
    int minWeightIndex = fromVertexIndex;

    // 已计算的顶点数量,初始值为0
    int calculatedNum;

    // 从fromVertexIndex到fromVertexIndex,令pathVal[fromVertexIndex]为0,令pre[fromVertexIndex]为fromVertexIndex
    // ,令calculated[fromVertexIndex]为true
    pathVal[fromVertexIndex] = (W) Integer.valueOf(0);
    pre[fromVertexIndex] = fromVertexIndex;
    calculated[fromVertexIndex] = true;
    calculatedNum = 1;

    while (calculatedNum < vertexNum) {
        // 循环到所有顶点都计算完毕

        // 处理本顶点指向的顶点
        vertexNode = vertexes[minWeightIndex];
        edgeNode = vertexNode.getFirstEdge();
        while (null != edgeNode) {
            int refI
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值