📜  所有对最短路径的约翰逊算法的实现

📅  最后修改于: 2022-05-13 01:56:07.684000             🧑  作者: Mango

所有对最短路径的约翰逊算法的实现

Johnson 的算法在加权有向图中找到所有顶点对之间的最短路径。它允许某些边缘权重为负数,但可能不存在负权重循环。它使用 Bellman-Ford 算法对原始图重新加权,去除所有负权重。 Dijkstra 算法应用于重新加权图以计算所有顶点对之间的最短路径。

算法描述

使用 Dijkstra 算法,可以找到O(V 2 logV)中所有顶点对之间的最短路径。但是,Dijkstra 不适用于负权重。为了避免这个问题,约翰逊的算法使用了一种称为重新加权的技术。

重新加权是改变每个边权重以满足两个属性的过程 -

  • 对于图中的所有顶点对u,v,如果这些顶点之间在重新加权之前存在最短路径,那么它也必须是重新加权之后这些顶点之间的最短路径。
  • 对于图中的所有边(u, v) ,它们必须具有非负权重(u, v)

Johnson 的算法使用 Bellman-Ford 重新加权边缘。如果原始图中存在负权重循环,Bellman-Ford 还能够检测到。

图形表示

对邻接列表进行了一些修改以表示图形。对于每个源顶点s ,它的每个相邻顶点都有两个与之关联的属性:

  1. 目的地
  2. 重量

考虑图表 -

源顶点0有一个相邻顶点,一个其目的地2权重 -2 。每个相邻顶点都使用静态Neighbor类进行封装。

Java
private static class Neighbour {
    int destination;
    int weight;
 
    Neighbour(int destination, int weight)
    {
        this.destination = destination;
        this.weight = weight;
    }
}


Java
// Java program for the above approach
import java.util.ArrayList;
import java.util.Arrays;
 
public class Graph {
    private static class Neighbour {
        int destination;
        int weight;
 
        Neighbour(int destination, int weight)
        {
            this.destination = destination;
            this.weight = weight;
        }
    }
 
    private int vertices;
    private final ArrayList >
        adjacencyList;
 
    // On using the below constructor,
    // edges must be added manually
    // to the graph using addEdge()
    public Graph(int vertices)
    {
        this.vertices = vertices;
 
        adjacencyList = new ArrayList<>(vertices);
        for (int i = 0; i < vertices; i++)
            adjacencyList.add(new ArrayList<>());
    }
 
    // On using the below constructor,
    // edges will be added automatically
    // to the graph using the adjacency matrix
    public Graph(int vertices, int[][] adjacencyMatrix)
    {
        this(vertices);
 
        for (int i = 0; i < vertices; i++) {
            for (int j = 0; j < vertices; j++) {
                if (adjacencyMatrix[i][j] != 0)
                    addEdge(i, j, adjacencyMatrix[i][j]);
            }
        }
    }
 
    public void addEdge(int source, int destination,
                        int weight)
    {
        adjacencyList.get(source).add(
            new Neighbour(destination, weight));
    }
 
    // Time complexity of this
    // implementation of dijkstra is O(V^2).
    public int[] dijkstra(int source)
    {
        boolean[] isVisited = new boolean[vertices];
        int[] distance = new int[vertices];
 
        Arrays.fill(distance, Integer.MAX_VALUE);
        distance = 0;
 
        for (int vertex = 0; vertex < vertices; vertex++) {
            int minDistanceVertex = findMinDistanceVertex(
                distance, isVisited);
            isVisited[minDistanceVertex] = true;
 
            for (Neighbour neighbour :
                 adjacencyList.get(minDistanceVertex)) {
                int destination = neighbour.destination;
                int weight = neighbour.weight;
 
                if (!isVisited[destination]
                    && distance[minDistanceVertex] + weight
                           < distance[destination])
                    distance[destination]
                        = distance[minDistanceVertex]
                          + weight;
            }
        }
 
        return distance;
    }
 
    // Method used by `int[] dijkstra(int)`
    private int findMinDistanceVertex(int[] distance,
                                      boolean[] isVisited)
    {
        int minIndex = -1,
            minDistance = Integer.MAX_VALUE;
 
        for (int vertex = 0; vertex < vertices; vertex++) {
            if (!isVisited[vertex]
                && distance[vertex] <= minDistance) {
                minDistance = distance[vertex];
                minIndex = vertex;
            }
        }
 
        return minIndex;
    }
 
    // Returns null if
    // negative weight cycle is detected
    public int[] bellmanford(int source)
    {
        int[] distance = new int[vertices];
 
        Arrays.fill(distance, Integer.MAX_VALUE);
        distance = 0;
 
        for (int i = 0; i < vertices - 1; i++) {
            for (int currentVertex = 0;
                 currentVertex < vertices;
                 currentVertex++) {
                for (Neighbour neighbour :
                     adjacencyList.get(currentVertex)) {
                    if (distance[currentVertex]
                            != Integer.MAX_VALUE
                        && distance[currentVertex]
                                   + neighbour.weight
                               < distance
                                     [neighbour
                                          .destination]) {
                        distance[neighbour.destination]
                            = distance[currentVertex]
                              + neighbour.weight;
                    }
                }
            }
        }
 
        for (int currentVertex = 0;
             currentVertex < vertices; currentVertex++) {
            for (Neighbour neighbour :
                 adjacencyList.get(currentVertex)) {
                if (distance[currentVertex]
                        != Integer.MAX_VALUE
                    && distance[currentVertex]
                               + neighbour.weight
                           < distance[neighbour
                                          .destination])
                    return null;
            }
        }
 
        return distance;
    }
 
    // Returns null if negative
    // weight cycle is detected
    public int[][] johnsons()
    {
        // Add a new vertex q to the original graph,
        // connected by zero-weight edges to
        // all the other vertices of the graph
 
        this.vertices++;
        adjacencyList.add(new ArrayList<>());
        for (int i = 0; i < vertices - 1; i++)
            adjacencyList.get(vertices - 1)
                .add(new Neighbour(i, 0));
 
        // Use bellman ford with the new vertex q
        // as source, to find for each vertex v
        // the minimum weight h(v) of a path
        // from q to v.
        // If this step detects a negative cycle,
        // the algorithm is terminated.
 
        int[] h = bellmanford(vertices - 1);
        if (h == null)
            return null;
 
        // Re-weight the edges of the original graph using the
        // values computed by the Bellman-Ford algorithm.
        // w'(u, v) = w(u, v) + h(u) - h(v).
 
        for (int u = 0; u < vertices; u++) {
            ArrayList neighbours
                = adjacencyList.get(u);
 
            for (Neighbour neighbour : neighbours) {
                int v = neighbour.destination;
                int w = neighbour.weight;
 
                // new weight
                neighbour.weight = w + h[u] - h[v];
            }
        }
 
        // Step 4: Remove edge q and apply Dijkstra
        // from each node s to every other vertex
        // in the re-weighted graph
 
        adjacencyList.remove(vertices - 1);
        vertices--;
 
        int[][] distances = new int[vertices][];
 
        for (int s = 0; s < vertices; s++)
            distances[s] = dijkstra(s);
 
        // Compute the distance in the original graph
        // by adding h[v] - h[u] to the
        // distance returned by dijkstra
 
        for (int u = 0; u < vertices; u++) {
            for (int v = 0; v < vertices; v++) {
 
                // If no edge exist, continue
                if (distances[u][v] == Integer.MAX_VALUE)
                    continue;
 
                distances[u][v] += (h[v] - h[u]);
            }
        }
 
        return distances;
    }
 
    // Driver Code
    public static void main(String[] args)
    {
        final int vertices = 4;
        final int[][] matrix = { { 0, 0, -2, 0 },
                                 { 4, 0, 3, 0 },
                                 { 0, 0, 0, 2 },
                                 { 0, -1, 0, 0 } };
 
        // Initialization
        Graph graph = new Graph(vertices, matrix);
 
        // Function Call
        int[][] distances = graph.johnsons();
 
        if (distances == null) {
            System.out.println(
                "Negative weight cycle detected.");
            return;
        }
 
        // The code fragment below outputs
        // an formatted distance matrix.
        // Its first row and first
        // column represent vertices
        System.out.println("Distance matrix:");
 
        System.out.print("   \t");
        for (int i = 0; i < vertices; i++)
            System.out.printf("%3d\t", i);
 
        for (int i = 0; i < vertices; i++) {
            System.out.println();
            System.out.printf("%3d\t", i);
            for (int j = 0; j < vertices; j++) {
                if (distances[i][j] == Integer.MAX_VALUE)
                    System.out.print(" X\t");
                else
                    System.out.printf("%3d\t",
                                      distances[i][j]);
            }
        }
    }
}


伪代码

请按照以下步骤解决问题:

  • 向图中添加一个新节点q ,通过零权重边连接到所有其他节点。
  • 使用 Bellman-Ford 算法,从新顶点 q 开始,为每个顶点 v 找到从 q 到 v 的路径的最小权重h(v) 。如果这一步检测到负循环,则算法终止。
  • 使用 Bellman-Ford 算法计算的值重新加权原始图的边:从 u 到 v 的边,长度w(u, v)重新加权为w(u, v) + h(u) − h(v ) .
  • 删除q并应用 Dijkstra 算法来找到从每个节点 s 到重新加权图中每个其他顶点的最短路径。
  • 通过将h(v) - h(u)添加到 Dijkstra 算法返回的距离来计算原始图中的距离。

下面是上述方法的实现:

Java

// Java program for the above approach
import java.util.ArrayList;
import java.util.Arrays;
 
public class Graph {
    private static class Neighbour {
        int destination;
        int weight;
 
        Neighbour(int destination, int weight)
        {
            this.destination = destination;
            this.weight = weight;
        }
    }
 
    private int vertices;
    private final ArrayList >
        adjacencyList;
 
    // On using the below constructor,
    // edges must be added manually
    // to the graph using addEdge()
    public Graph(int vertices)
    {
        this.vertices = vertices;
 
        adjacencyList = new ArrayList<>(vertices);
        for (int i = 0; i < vertices; i++)
            adjacencyList.add(new ArrayList<>());
    }
 
    // On using the below constructor,
    // edges will be added automatically
    // to the graph using the adjacency matrix
    public Graph(int vertices, int[][] adjacencyMatrix)
    {
        this(vertices);
 
        for (int i = 0; i < vertices; i++) {
            for (int j = 0; j < vertices; j++) {
                if (adjacencyMatrix[i][j] != 0)
                    addEdge(i, j, adjacencyMatrix[i][j]);
            }
        }
    }
 
    public void addEdge(int source, int destination,
                        int weight)
    {
        adjacencyList.get(source).add(
            new Neighbour(destination, weight));
    }
 
    // Time complexity of this
    // implementation of dijkstra is O(V^2).
    public int[] dijkstra(int source)
    {
        boolean[] isVisited = new boolean[vertices];
        int[] distance = new int[vertices];
 
        Arrays.fill(distance, Integer.MAX_VALUE);
        distance = 0;
 
        for (int vertex = 0; vertex < vertices; vertex++) {
            int minDistanceVertex = findMinDistanceVertex(
                distance, isVisited);
            isVisited[minDistanceVertex] = true;
 
            for (Neighbour neighbour :
                 adjacencyList.get(minDistanceVertex)) {
                int destination = neighbour.destination;
                int weight = neighbour.weight;
 
                if (!isVisited[destination]
                    && distance[minDistanceVertex] + weight
                           < distance[destination])
                    distance[destination]
                        = distance[minDistanceVertex]
                          + weight;
            }
        }
 
        return distance;
    }
 
    // Method used by `int[] dijkstra(int)`
    private int findMinDistanceVertex(int[] distance,
                                      boolean[] isVisited)
    {
        int minIndex = -1,
            minDistance = Integer.MAX_VALUE;
 
        for (int vertex = 0; vertex < vertices; vertex++) {
            if (!isVisited[vertex]
                && distance[vertex] <= minDistance) {
                minDistance = distance[vertex];
                minIndex = vertex;
            }
        }
 
        return minIndex;
    }
 
    // Returns null if
    // negative weight cycle is detected
    public int[] bellmanford(int source)
    {
        int[] distance = new int[vertices];
 
        Arrays.fill(distance, Integer.MAX_VALUE);
        distance = 0;
 
        for (int i = 0; i < vertices - 1; i++) {
            for (int currentVertex = 0;
                 currentVertex < vertices;
                 currentVertex++) {
                for (Neighbour neighbour :
                     adjacencyList.get(currentVertex)) {
                    if (distance[currentVertex]
                            != Integer.MAX_VALUE
                        && distance[currentVertex]
                                   + neighbour.weight
                               < distance
                                     [neighbour
                                          .destination]) {
                        distance[neighbour.destination]
                            = distance[currentVertex]
                              + neighbour.weight;
                    }
                }
            }
        }
 
        for (int currentVertex = 0;
             currentVertex < vertices; currentVertex++) {
            for (Neighbour neighbour :
                 adjacencyList.get(currentVertex)) {
                if (distance[currentVertex]
                        != Integer.MAX_VALUE
                    && distance[currentVertex]
                               + neighbour.weight
                           < distance[neighbour
                                          .destination])
                    return null;
            }
        }
 
        return distance;
    }
 
    // Returns null if negative
    // weight cycle is detected
    public int[][] johnsons()
    {
        // Add a new vertex q to the original graph,
        // connected by zero-weight edges to
        // all the other vertices of the graph
 
        this.vertices++;
        adjacencyList.add(new ArrayList<>());
        for (int i = 0; i < vertices - 1; i++)
            adjacencyList.get(vertices - 1)
                .add(new Neighbour(i, 0));
 
        // Use bellman ford with the new vertex q
        // as source, to find for each vertex v
        // the minimum weight h(v) of a path
        // from q to v.
        // If this step detects a negative cycle,
        // the algorithm is terminated.
 
        int[] h = bellmanford(vertices - 1);
        if (h == null)
            return null;
 
        // Re-weight the edges of the original graph using the
        // values computed by the Bellman-Ford algorithm.
        // w'(u, v) = w(u, v) + h(u) - h(v).
 
        for (int u = 0; u < vertices; u++) {
            ArrayList neighbours
                = adjacencyList.get(u);
 
            for (Neighbour neighbour : neighbours) {
                int v = neighbour.destination;
                int w = neighbour.weight;
 
                // new weight
                neighbour.weight = w + h[u] - h[v];
            }
        }
 
        // Step 4: Remove edge q and apply Dijkstra
        // from each node s to every other vertex
        // in the re-weighted graph
 
        adjacencyList.remove(vertices - 1);
        vertices--;
 
        int[][] distances = new int[vertices][];
 
        for (int s = 0; s < vertices; s++)
            distances[s] = dijkstra(s);
 
        // Compute the distance in the original graph
        // by adding h[v] - h[u] to the
        // distance returned by dijkstra
 
        for (int u = 0; u < vertices; u++) {
            for (int v = 0; v < vertices; v++) {
 
                // If no edge exist, continue
                if (distances[u][v] == Integer.MAX_VALUE)
                    continue;
 
                distances[u][v] += (h[v] - h[u]);
            }
        }
 
        return distances;
    }
 
    // Driver Code
    public static void main(String[] args)
    {
        final int vertices = 4;
        final int[][] matrix = { { 0, 0, -2, 0 },
                                 { 4, 0, 3, 0 },
                                 { 0, 0, 0, 2 },
                                 { 0, -1, 0, 0 } };
 
        // Initialization
        Graph graph = new Graph(vertices, matrix);
 
        // Function Call
        int[][] distances = graph.johnsons();
 
        if (distances == null) {
            System.out.println(
                "Negative weight cycle detected.");
            return;
        }
 
        // The code fragment below outputs
        // an formatted distance matrix.
        // Its first row and first
        // column represent vertices
        System.out.println("Distance matrix:");
 
        System.out.print("   \t");
        for (int i = 0; i < vertices; i++)
            System.out.printf("%3d\t", i);
 
        for (int i = 0; i < vertices; i++) {
            System.out.println();
            System.out.printf("%3d\t", i);
            for (int j = 0; j < vertices; j++) {
                if (distances[i][j] == Integer.MAX_VALUE)
                    System.out.print(" X\t");
                else
                    System.out.printf("%3d\t",
                                      distances[i][j]);
            }
        }
    }
}
输出
Distance matrix:
         0      1      2      3    
  0      0     -1     -2      0    
  1      4      0      2      4    
  2      5      1      0      2    
  3      3     -1      1      0    

时间复杂度: O(V 2 log V + VE),当图完整时,Johnson算法的时间复杂度与Floyd Warshall相同(对于完整图E = O(V 2 )。但对于稀疏图,该算法表现比Floyd Warshall好得多
辅助空间: O(V*V)