1
votes

I am trying to find shortest pair of edge-disjoint paths between a given pair of vertices and I was following this algorithm which in general is Suurballe's algorithm I guess.

Here is the algorithm:

  • Run the shortest path algorithm for the given pair of vertices (I am using Dijkstra algorithm)
  • Replace each edge of the shortest path (equivalent to two oppositely directed arcs) by a single arc directed towards the source vertex
  • Make the length of each of the above arcs negative
  • Run the shortest path algorithm (Note: the algorithm should accept negative costs)
  • Erase the overlapping edges of the two paths found, and reverse the direction of the remaining arcs on the first shortest path such that each arc on it is directed towards the sink vertex now. The desired pair of paths results.

In that wikipedia, first step is to find the shortest path between source and destination node which I am able to do it correctly as shown in the below code by using Dijkstra Algorithm -

public class DijkstraAlgorithm {

    private static final Graph.Edge[] GRAPH = { 
        new Graph.Edge("A", "G", 8), 
        new Graph.Edge("A", "B", 1), 
        new Graph.Edge("A", "E", 1), 
        new Graph.Edge("B", "C", 1), 
        new Graph.Edge("B", "E", 1),
        new Graph.Edge("B", "F", 2),
        new Graph.Edge("C", "G", 1),
        new Graph.Edge("C", "D", 1),
        new Graph.Edge("D", "F", 1),
        new Graph.Edge("D", "Z", 1),
        new Graph.Edge("E", "F", 4),
        new Graph.Edge("F", "Z", 4),
        new Graph.Edge("G", "Z", 2),
    };

    private static final String START = "A";
    private static final String END = "Z";

    public static void main(String[] args) {
        Graph g = new Graph(GRAPH);
        g.dijkstra(START);
        //  print the shortest path using Dijkstra algorithm
        g.printPath(END);
        //        g.printAllPaths();
    }
}


class Graph {
    private final Map<String, Vertex> graph; // mapping of vertex names to Vertex objects, built from a set of Edges

    /** One edge of the graph (only used by Graph constructor) */
    public static class Edge {
        public final String v1, v2;
        public final int dist;

        public Edge(String v1, String v2, int dist) {
            this.v1 = v1;
            this.v2 = v2;
            this.dist = dist;
        }
    }

    /** One vertex of the graph, complete with mappings to neighbouring vertices */
    public static class Vertex implements Comparable<Vertex> {
        public final String name;
        public int dist = Integer.MAX_VALUE; // MAX_VALUE assumed to be infinity
        public Vertex previous = null;
        public final Map<Vertex, Integer> neighbours = new HashMap<Vertex, Integer>();

        public Vertex(String name) {
            this.name = name;
        }

        private void printPath() {
            if (this == this.previous) {
                System.out.printf("%s", this.name);
            } else if (this.previous == null) {
                System.out.printf("%s(unreached)", this.name);
            } else {
                this.previous.printPath();
                System.out.printf(" -> %s(%d)", this.name, this.dist);
            }
        }

        public int compareTo(Vertex other) {
            if (dist==other.dist)
                return name.compareTo(other.name);
            return Integer.compare(dist, other.dist);
        }
    }

    /** Builds a graph from a set of edges */
    public Graph(Edge[] edges) {
        graph = new HashMap<String, Vertex>(edges.length);

        //one pass to find all vertices
        for (Edge e : edges) {
            if (!graph.containsKey(e.v1))
                graph.put(e.v1, new Vertex(e.v1));
            if (!graph.containsKey(e.v2))
                graph.put(e.v2, new Vertex(e.v2));
        }

        //another pass to set neighbouring vertices
        for (Edge e : edges) {
            graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist);
            graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also for an undirected graph
        }
    }

    /** Runs dijkstra using a specified source vertex */
    public void dijkstra(String startName) {
        if (!graph.containsKey(startName)) {
            System.err.printf("Graph doesn't contain start vertex \"%s\"\n", startName);
            return;
        }
        final Vertex source = graph.get(startName);
        NavigableSet<Vertex> q = new TreeSet<Vertex>();

        // set-up vertices
        for (Vertex v : graph.values()) {
            v.previous = v == source ? source : null;
            v.dist = v == source ? 0 : Integer.MAX_VALUE;
            q.add(v);
        }

        dijkstra(q);
    }

    /** Implementation of dijkstra's algorithm using a binary heap. */
    private void dijkstra(final NavigableSet<Vertex> q) {
        Vertex u, v;
        while (!q.isEmpty()) {

            u = q.pollFirst(); // vertex with shortest distance (first iteration will return source)
            if (u.dist == Integer.MAX_VALUE)
                break; // we can ignore u (and any other remaining vertices) since they are unreachable

            //look at distances to each neighbour
            for (Map.Entry<Vertex, Integer> a : u.neighbours.entrySet()) {
                v = a.getKey(); //the neighbour in this iteration

                final int alternateDist = u.dist + a.getValue();
                if (alternateDist < v.dist) { // shorter path to neighbour found
                    q.remove(v);
                    v.dist = alternateDist;
                    v.previous = u;
                    q.add(v);
                }
            }
        }
    }

    /** Prints a path from the source to the specified vertex */
    public void printPath(String endName) {
        if (!graph.containsKey(endName)) {
            System.err.printf("Graph doesn't contain end vertex \"%s\"\n", endName);
            return;
        }

        graph.get(endName).printPath();
        System.out.println();
    }

    /** Prints the path from the source to every vertex (output order is not guaranteed) */
    public void printAllPaths() {
        for (Vertex v : graph.values()) {
            v.printPath();
            System.out.println();
        }
    }
}

Now I am stuck in executing the remaining steps in that algorithm so that I can get shortest pair of edge-disjoint paths between a given pair of vertices.

The shortest path from node A to node Z is ABCDZ, while the shortest pair is ABCGZ and AEBFDZ.

1

1 Answers

2
votes

I will not write the code, but I can explain to you how to approach the problem, and also give a basic idea why it works. I will use terms source and sink for the two nodes you are searching the path between.

First, why it works. As you noticed with your example, the shortest pair of paths does not necessarily contain the single shortest path in it. Moreover, if you find the shortest path and remove it, you can find yourself in a situation, when no path from the source to the sink exists that is edge-disjoint with the shortest path you just found. So we need to find a way to be able to change the first path we found as we build the second path. In fact, this is exactly what we achieve by adding the negative weights.

Let's consider it with your example. You ran the first dijkstra, and it found the path ABCDZ for you. Now as we run the second dijkstra, we want to find another path, AEBFDZ, and at the same time modify ABCDZ into ABCGZ. Here's how it happens: after the first dijstra you reverse all the edges in that path and negate their weights. For instance, edge A->B with weight 1 becomes edge B->A with weight -1. It should be easy to do -- you already have logic that restores the path. As you restore the path, remove edges that it consists of, and add them back reversed with negated weight.

For your particular example we only care about the edge C->D with weight 1. After we ran the first dijkstra, it was reversed, and became an edge D->C with weight -1. Now as we try to find a second shortest path, it will find a path AEFDCGZ. Note that it contains the edge D->C, the one we just added. What does it mean to use such an edge with a negative weight in the second path? Well, try to draw it on the piece of paper, you will see that the first path goes like A-B-C ----- D-Z, and the second goes like A-E-F-D ---- C-G-Z. If you draw it, you will notice that you can remove that edge (C-D) from both paths, and swap their tails. As you do that, the sum of the path weights will not change (because the first path has that edge with positive weight, and the second path has it with negative weight), resulting in two paths A-B-C-G-Z and A-E-F-D-Z, precisely the two paths you were looking for.

Now let's take a look at how to approach this problem implementation wise. You will need three separate stages. I could've written the code for you, but I am sure you will learn way more by attacking it yourself.

Stage 1. You will need to implement the logic of reversing the edges. As I said, it is very straightforward. You just restore the path after the first dijkstra (you already have a logic for doing that), and for every edge in that path you delete it from the graph, and then add it back reversed and negated.

Stage 2. You need a function that can find a shortest path in a graph with negative weights. It is very important to note that dijkstra does not work on graphs with negative weights. So here we have two approaches: approach (a) is to use bellman-ford algorithm. It is slower than dijkstra, but does exactly that: finds a shortest path in a graph with negative weights. Approach (b) is less known, but is faster, and leverages the way we introduced those negative weights. The way it works is as follows:

  1. First, as you run the first dijkstra, do not stop when you reach the sink, keep on traversing the graph and assigning the distances to the nodes. At the end of the first dijskstra each node will have a distance from the source assigned to it. Copy these values into a new array, called pt. We call these values 'potentials'.

  2. Remove edges that belong to the first path and add their reversed and negated copies (perform stage 1)

  3. After that, change the weight w[i, j] of every edge in the graph to w'[i, j] = w[i, j] + pt[i] - pt[j], where pt[i] is a potential of vertex i. Interestingly, the new graph with weights w' will have two properties: it will not have any negative weights, and the shortest path between source and sink in the original graph will be the shortest path between source and sink in the new graph (and vice versa). Now you can run dijkstra in this new graph (because it has no negative weights), and be sure that the shortest path you find in it corresponds to the shortest path in the original graph.

Now at this time you should be able to get paths A-B-C-D-Z and A-E-F-D-C-G-Z for your example graph. Be sure to get to this stage before you move to stage 3.

Stage 3: As you finish with stage 2, the last stage will be to implement proper recovery of the paths. Given two paths from stage 2 you will need to find all the negative weights used in the second path, and reconnect edges between paths. There's a simpler alternative, again, which is for each edge to track whether it belongs to one of the two paths or not. If you add a certain edge to one of the two paths with positive edge, you mark it as belonging to one of the paths, and as you add it with negative weight, you mark it as not belonging. In your example the edge C-D will first be marked as belonging to one of the paths when you find the first path, and then as not belonging, as you find the second path and add D-C to it. When you have such marks, you can just recover two paths by traversing marked edges from the source to the sink in any order.

EDIT: Here's java code. Note that besides new methods that I introduced, there's a change in the actual dijkstra method. Namely, it now uses potentials when computes the alternativeDist. The way I restore paths seems a little bit over complicated, there's probably an easier way. I currently store a treeset of all the edges that belong to the answer. If I am trying to add an edge, for which its reverse is already in the answer, I instead remove it from the answer (it is a traversal of a negated edge). Then I just restore the answer based on that treeset.

import java.util.*;

public class DijkstraAlgorithm {

    private static final Graph.Edge[] GRAPH = { 
        new Graph.Edge("A", "G", 8), 
        new Graph.Edge("A", "B", 1), 
        new Graph.Edge("A", "E", 1), 
        new Graph.Edge("B", "C", 1), 
        new Graph.Edge("B", "E", 1),
        new Graph.Edge("B", "F", 2),
        new Graph.Edge("C", "G", 1),
        new Graph.Edge("C", "D", 1),
        new Graph.Edge("D", "F", 1),
        new Graph.Edge("D", "Z", 1),
        new Graph.Edge("E", "F", 4),
        new Graph.Edge("F", "Z", 4),
        new Graph.Edge("G", "Z", 2),
    };

    private static final String START = "A";
    private static final String END = "Z";

    public static void main(String[] args) {
        Graph g = new Graph(GRAPH);
        g.dijkstra(START);
        g.restorePath(END);
        g.revertEdges(END);
        g.assignPotentials();
        g.dijkstra(START);
        g.restorePath(END);

        g.printPaths(START, END);
    }
}


class Graph {
    private final Map<String, Vertex> graph; // mapping of vertex names to Vertex objects, built from a set of Edges

    /** One edge of the graph (only used by Graph constructor) */
    public static class Edge implements Comparable<Edge> {
        public final String v1, v2;
        public final int dist;

        public Edge(String v1, String v2, int dist) {
            this.v1 = v1;
            this.v2 = v2;
            this.dist = dist;
        }

        public int compareTo(Edge other) {
            if (v1.equals(other.v1))
                return v2.compareTo(other.v2);
            return v1.compareTo(other.v1);
        }
    }

    private TreeSet<Edge> answer = new TreeSet<Edge>(); // stores all the edges in the answer

    /** One vertex of the graph, complete with mappings to neighbouring vertices */
    public static class Vertex implements Comparable<Vertex> {
        public final String name;
        public int potential = 0; // is assigned to dist before the second dijkstra
        public int dist = Integer.MAX_VALUE; // MAX_VALUE assumed to be infinity
        public Vertex previous = null;
        public final Map<Vertex, Integer> neighbours = new HashMap<Vertex, Integer>();

        public Vertex(String name) {
            this.name = name;
        }

        public int compareTo(Vertex other) {
            if (dist==other.dist)
                return name.compareTo(other.name);
            return Integer.compare(dist, other.dist);
        }
    }

    /** Builds a graph from a set of edges */
    public Graph(Edge[] edges) {
        graph = new HashMap<String, Vertex>(edges.length);

        //one pass to find all vertices
        for (Edge e : edges) {
            if (!graph.containsKey(e.v1))
                graph.put(e.v1, new Vertex(e.v1));
            if (!graph.containsKey(e.v2))
                graph.put(e.v2, new Vertex(e.v2));
        }

        //another pass to set neighbouring vertices
        for (Edge e : edges) {
            graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist);
            graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also for an undirected graph
        }
    }

    /** Runs dijkstra using a specified source vertex */
    public void dijkstra(String startName) {
        if (!graph.containsKey(startName)) {
            System.err.printf("Graph doesn't contain start vertex \"%s\"\n", startName);
            return;
        }
        final Vertex source = graph.get(startName);
        NavigableSet<Vertex> q = new TreeSet<Vertex>();

        // set-up vertices
        for (Vertex v : graph.values()) {
            v.previous = v == source ? source : null;
            v.dist = v == source ? 0 : Integer.MAX_VALUE;
            q.add(v);
        }

        dijkstra(q);
    }

    /** Implementation of dijkstra's algorithm using a binary heap. */
    private void dijkstra(final NavigableSet<Vertex> q) {
        Vertex u, v;
        while (!q.isEmpty()) {

            u = q.pollFirst(); // vertex with shortest distance (first iteration will return source)
            if (u.dist == Integer.MAX_VALUE)
                break; // we can ignore u (and any other remaining vertices) since they are unreachable

            //look at distances to each neighbour
            for (Map.Entry<Vertex, Integer> a : u.neighbours.entrySet()) {
                v = a.getKey(); //the neighbour in this iteration

                final int alternateDist = u.dist + a.getValue() + u.potential - v.potential;
                if (alternateDist < v.dist) { // shorter path to neighbour found
                    q.remove(v);
                    v.dist = alternateDist;
                    v.previous = u;
                    q.add(v);
                }
            }
        }
    }

    /** Prints a path from the source to the specified vertex */
    public void revertEdges(String endName) {
        Vertex v = graph.get(endName);
        while (v.previous != null && v.previous != v) {
            Vertex w = v.previous;
            int weight = v.neighbours.get(w);
            v.neighbours.remove(w);
            w.neighbours.remove(v);

            v.neighbours.put(w, - weight);

            v = w;
        }
    }

    public void assignPotentials() {
        for (Vertex v : graph.values()) {
            v.potential = v.dist;
        }
    }

    /** Stores the path found by dijkstra into the answer */
    public void restorePath(String endName) {
        Vertex v = graph.get(endName);
        while (v.previous != null && v.previous != v) {
            String from = v.previous.name;
            String to = v.name;
            if (answer.contains(new Edge(to, from, 0))) {
                answer.remove(new Edge(to, from, 0));
            }
            else {
                answer.add(new Edge(from, to, 0));
            }
            v = v.previous;
        }
    }

    /** Restores and prints one path based on `answer` dictionary, and removes the edges restored from the answer */
    public void printOnePath(String startName, String endName) {
        Vertex from = graph.get(startName);
        Vertex to = graph.get(endName);
        Vertex cur = from;
        do {
            System.out.printf("%s -> ", cur.name);

            Edge e = answer.ceiling(new Edge(cur.name, "", 0));
            answer.remove(e);

            cur = graph.get(e.v2);
        } while (cur != to);
        System.out.println(to.name);
    }

    /** Restores and prints paths based on `answer` dicrionary */
    public void printPaths(String startName, String endName) {
        printOnePath(startName, endName);
        printOnePath(startName, endName);
    }
}