SPFA算法

package com.company;

public class Main {

    public static void main(String[] args) {
    // write your code here
        int[][] testMatrix = {
                {-1,-1,10,-1,30,100},
                {-1,-1,5,-1,-1,-1},
                {-1,-1,-1,50,-1,-1},
                {-1,-1,-1,-1,0,10},
                {-1,-1,-1,20,-1,60},
                {-1,-1,-1,-1,-1,-1}
        };
        int[][] testMatrix0 = {
                {-1,24,8,15,-1,-1,-1},
                {-1,-1,-1,-1,6,-1,-1},
                {-1,-1,-1,-1,7,3,-1},
                {-1,-1,-1,-1,-1,-1,4},
                {-1,-1,-1,-1,-1,-1,9},
                {-1,-1,-1,-1,2,-1,3},
                {-1,3,-1,-1,-1,-1,-1}
        };
        Spfa.spfa0(testMatrix,0);
    }
}

package com.company;

public class Spfa {
    /**
     * 适用于单源最短路径问题,尤其适用于权值为负的情况。
     * 它适用于有向图。
     * 但是如果有向图中存在回路,并且回路的权值之和为负,
     * 那么本图是无法用此算法求最短路径的,因为越求越小,
     * 根本没有最小路径。
     * 本算法的实质是一个广度优先搜索算法。
     * 又叫动态逼近法。
     * 为什么spfa所有队列中如果有一个结点,那么相同的结
     * 点就不入队呢?
     * 因为入队是因为以该顶点为中间点能够找到更短路径,
     * 入队是要传达一个信息,该顶点具备可以使路径变得更
     * 短的能力,它是一个标识而已,而有一个在队列中就能
     * 够标识该顶点能够使路径变得更短了,就不需要第二个
     * 了。
     * 其他的也可能是为了不重复入队,减少一些不必要的操作吧,或
     * 者限制队列的长度吧,这样确实可以确定地设置队列的
     * 长度。
     * 出去的点作为中间点,如果该顶点的出度加上它已有的
     * 最小路径比从源点到其箭头顶点的距离更短,则更新从
     * 源点到箭头顶点最短路径的值。
     * @param sourceArray
     * @param vertexId
     */
    static public void spfa0(int[][] sourceArray,int vertexId) {
        int arrayLength = sourceArray.length;
        int maxValue = 0;
        //获取最大值
        for (int counter = 0;counter < arrayLength;counter++)
            for (int counter0 = 0;counter0 < arrayLength;counter0++)
                if (sourceArray[counter][counter0] > 0)
                    maxValue += sourceArray[counter][counter0];
        int[] minPathArray = new int[arrayLength];
        //这里以maxValue代表无穷大。
        for (int counter = 0;counter < arrayLength;counter++) {
            if (vertexId == counter)minPathArray[counter] = 0;
            else minPathArray[counter] = maxValue;
        }
        //用来存储前驱的中间结点数组,也是结果集数组。
        int[] preMiddleArray = new int[arrayLength];
        //也把它初始化为-1
        for (int counter = 0;counter < arrayLength;counter++)
            preMiddleArray[counter] = -1;
        //为了不让已经在队列中的顶点标号重复入队,所以
        // 应该弄一个数组来标记有哪个顶点入队了。
        boolean[] vertexFlag = new boolean[arrayLength];
        for (int counter = 0;counter < arrayLength;counter++)
            vertexFlag[counter] = false;
        //由于队列中并不存在重复元素,
        // 所以队列最长为arrayLength。
        // 并且采用循环队列。
        int[] minPathQueue = new int[arrayLength];
        int front = 0;
        int rear = front;
        minPathQueue[rear] = vertexId;
        vertexFlag[vertexId] = true;
        System.out.println("标记数组的状态是");
        for (boolean element:vertexFlag)
            System.out.print((element?"O":"X") + " ");
        System.out.println();
        while ((rear + 1) % arrayLength != front) {
            System.out.println("最短路径数组状态");
            for (int element:minPathArray)
                System.out.print(element + " ");
            System.out.println("\n前驱结点状态");
            for (int element:preMiddleArray)
                System.out.print(element + " ");
            int currentMiddleIndex = minPathQueue[front++];
            vertexFlag[currentMiddleIndex] = false;
            System.out.println("\n顶点" + currentMiddleIndex + "出队");
            System.out.println("并把顶点" + currentMiddleIndex + "标记为X");
            System.out.println("标记数组的状态是");
            for (boolean element:vertexFlag)
                System.out.print((element?"O":"X") + " ");
            System.out.println();
            front = front % arrayLength;
            for (int counter = 0;counter < arrayLength;counter++) {
                //这个比较的术语叫做松弛,所以这一操作被称为松弛操作。
                // 很形象,最初两个顶点之间有根橡皮筋,后来每次找到一
                // 个能够使路径更短的中间点,就让橡皮筋更松弛一些,于
                // 是松弛操作的直接意思就是找到了个中间点让原来两顶点
                // 之间的路径更短了。
                if (sourceArray[currentMiddleIndex][counter] > 0
                        && minPathArray[currentMiddleIndex]
                        + sourceArray[currentMiddleIndex][counter]
                        < minPathArray[counter]) {
                    if (!vertexFlag[counter]) {
                        minPathQueue[++rear % arrayLength] = counter;
                        vertexFlag[counter] = true;
                        System.out.println("顶点" + counter + "入队并被标记为O");
                    } else System.out.println("顶点" + counter + "无需重复入队");
                    System.out.println("标记数组的状态是");
                    for (boolean element:vertexFlag)
                        System.out.print((element?"O":"X") + " ");
                    System.out.println();
                    minPathArray[counter] = minPathArray[currentMiddleIndex]
                            + sourceArray[currentMiddleIndex][counter];
                    preMiddleArray[counter] = currentMiddleIndex;
                    System.out.println("最短路径数组状态");
                    for (int element:minPathArray)
                        System.out.print(element + " ");
                    System.out.println("\n前驱结点状态");
                    for (int element:preMiddleArray)
                        System.out.print(element + " ");
                    System.out.println();
                }
            }
            System.out.println("顶点" + currentMiddleIndex + "处理完毕");
            System.out.println("最短路径数组状态");
            for (int element:minPathArray)
                System.out.print(element + " ");
            System.out.println("\n前驱结点状态");
            for (int element:preMiddleArray)
                System.out.print(element + " ");
            System.out.println("\n");
        }
        System.out.println("此时队列为空");
        //下面打印从源顶点到各顶点的最短路径
        System.out.println("结果集为:");
        int[] minPathStack = new int[arrayLength];
        for (int counter = 0;counter < arrayLength;counter++) {
            if (counter == vertexId)continue;
            System.out.print("(" + vertexId + "," + counter + "):");
            if (minPathArray[counter] == maxValue) {
                System.out.println("不可达");
                continue;
            }
            int pathStackTopPointer = -1;
            int preIndex = counter;
            while (preMiddleArray[preIndex] != vertexId) {
                minPathStack[++pathStackTopPointer] = preIndex;
                preIndex = preMiddleArray[preIndex];
            }
            minPathStack[++pathStackTopPointer] = preIndex;
            minPathStack[++pathStackTopPointer] = vertexId;
            while (pathStackTopPointer > -1) {
                if (counter == minPathStack[pathStackTopPointer])
                    System.out.print(minPathStack[pathStackTopPointer--]);
                else System.out.print(minPathStack[pathStackTopPointer--] + "-->");
            }
            System.out.println();
        }
    }
}

输出

标记数组的状态是
O X X X X X 
最短路径数组状态
0 285 285 285 285 285 
前驱结点状态
-1 -1 -1 -1 -1 -1 
顶点0出队
并把顶点0标记为X
标记数组的状态是
X X X X X X 
顶点2入队并被标记为O
标记数组的状态是
X X O X X X 
最短路径数组状态
0 285 10 285 285 285 
前驱结点状态
-1 -1 0 -1 -1 -1 
顶点4入队并被标记为O
标记数组的状态是
X X O X O X 
最短路径数组状态
0 285 10 285 30 285 
前驱结点状态
-1 -1 0 -1 0 -1 
顶点5入队并被标记为O
标记数组的状态是
X X O X O O 
最短路径数组状态
0 285 10 285 30 100 
前驱结点状态
-1 -1 0 -1 0 0 
顶点0处理完毕
最短路径数组状态
0 285 10 285 30 100 
前驱结点状态
-1 -1 0 -1 0 0 

最短路径数组状态
0 285 10 285 30 100 
前驱结点状态
-1 -1 0 -1 0 0 
顶点2出队
并把顶点2标记为X
标记数组的状态是
X X X X O O 
顶点3入队并被标记为O
标记数组的状态是
X X X O O O 
最短路径数组状态
0 285 10 60 30 100 
前驱结点状态
-1 -1 0 2 0 0 
顶点2处理完毕
最短路径数组状态
0 285 10 60 30 100 
前驱结点状态
-1 -1 0 2 0 0 

最短路径数组状态
0 285 10 60 30 100 
前驱结点状态
-1 -1 0 2 0 0 
顶点4出队
并把顶点4标记为X
标记数组的状态是
X X X O X O 
顶点3无需重复入队
标记数组的状态是
X X X O X O 
最短路径数组状态
0 285 10 50 30 100 
前驱结点状态
-1 -1 0 4 0 0 
顶点5无需重复入队
标记数组的状态是
X X X O X O 
最短路径数组状态
0 285 10 50 30 90 
前驱结点状态
-1 -1 0 4 0 4 
顶点4处理完毕
最短路径数组状态
0 285 10 50 30 90 
前驱结点状态
-1 -1 0 4 0 4 

最短路径数组状态
0 285 10 50 30 90 
前驱结点状态
-1 -1 0 4 0 4 
顶点5出队
并把顶点5标记为X
标记数组的状态是
X X X O X X 
顶点5处理完毕
最短路径数组状态
0 285 10 50 30 90 
前驱结点状态
-1 -1 0 4 0 4 

最短路径数组状态
0 285 10 50 30 90 
前驱结点状态
-1 -1 0 4 0 4 
顶点3出队
并把顶点3标记为X
标记数组的状态是
X X X X X X 
顶点5入队并被标记为O
标记数组的状态是
X X X X X O 
最短路径数组状态
0 285 10 50 30 60 
前驱结点状态
-1 -1 0 4 0 3 
顶点3处理完毕
最短路径数组状态
0 285 10 50 30 60 
前驱结点状态
-1 -1 0 4 0 3 

最短路径数组状态
0 285 10 50 30 60 
前驱结点状态
-1 -1 0 4 0 3 
顶点5出队
并把顶点5标记为X
标记数组的状态是
X X X X X X 
顶点5处理完毕
最短路径数组状态
0 285 10 50 30 60 
前驱结点状态
-1 -1 0 4 0 3 

此时队列为空
结果集为:
(0,1):不可达
(0,2):0-->2
(0,3):0-->4-->3
(0,4):0-->4
(0,5):0-->4-->3-->5
    原文作者:Stroman
    原文地址: https://www.jianshu.com/p/0ea876a909bf
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞