Dijkstra Algorithm and Source Change in Java

Viewed 1219

So, I am trying to implement the Dijkstra Algorithm in order to find the shortest path between two cities. So far my classes are :

Edge.java

package com.company;

public class Edge {

        private int weight;
        private Vertex startVertex;
        private Vertex targetVertex;

        public Edge(int weight, Vertex startVertex, Vertex targetVertex) {
            this.weight = weight;
            this.startVertex = startVertex;
            this.targetVertex = targetVertex;
        }

        public double getWeight() {
            return weight;
        }

        public void setWeight(int weight) {
            this.weight = weight;
        }

        public Vertex getStartVertex() {
            return startVertex;
        }

        public void setStartVertex(Vertex startVertex) {
            this.startVertex = startVertex;
        }

        public Vertex getTargetVertex() {
            return targetVertex;
        }

        public void setTargetVertex(Vertex targetVertex) {
            this.targetVertex = targetVertex;
        }
    }

then the Vertex.java

package com.company;

import java.util.ArrayList;
import java.util.List;

public class Vertex implements Comparable<Vertex> {

    private String name;
    private List<Edge> adjacenciesList;
    private boolean visited;
    private Vertex predecessor;
    private double distance = Double.MAX_VALUE;

    public Vertex(String name) {
        this.name = name;
        this.adjacenciesList = new ArrayList<>();
    }

    public void addNeighbour(Edge edge) {
        this.adjacenciesList.add(edge);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Edge> getAdjacenciesList() {
        return adjacenciesList;
    }

    public void setAdjacenciesList(List<Edge> adjacenciesList) {
        this.adjacenciesList = adjacenciesList;
    }

    public boolean isVisited() {
        return visited;
    }

    public void setVisited(boolean visited) {
        this.visited = visited;
    }

    public Vertex getPredecessor() {
        return predecessor;
    }

    public void setPredecessor(Vertex predecessor) {
        this.predecessor = predecessor;
    }

    public double getDistance() {
        return distance;
    }

    public void setDistance(double distance) {
        this.distance = distance;
    }

    @Override
    public String toString() {
        return this.name;
    }

    @Override
    public int compareTo(Vertex otherVertex) {
        return Double.compare(this.distance, otherVertex.getDistance());
    }
}

and DijkstraShortestPath.java

    package com.company;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;

public class DjikstraShortestPath {
        public void computeShortestPaths(Vertex sourceVertex){

            sourceVertex.setDistance(0);
            PriorityQueue<Vertex> priorityQueue = new PriorityQueue<>();
            priorityQueue.add(sourceVertex);
            sourceVertex.setVisited(true);

            while( !priorityQueue.isEmpty() ){
                // Getting the minimum distance vertex from priority queue
                Vertex actualVertex = priorityQueue.poll();

                for(Edge edge : actualVertex.getAdjacenciesList()){

                    Vertex v = edge.getTargetVertex();
                    if(!v.isVisited())
                    {
                        double newDistance = actualVertex.getDistance() + edge.getWeight();

                        if( newDistance < v.getDistance() ){
                            priorityQueue.remove(v);
                            v.setDistance(newDistance);
                            v.setPredecessor(actualVertex);
                            priorityQueue.add(v);
                        }
                    }
                }
                actualVertex.setVisited(true);
            }
        }

        public List<Vertex> getShortestPathTo(Vertex targetVertex){
            List<Vertex> path = new ArrayList<>();

            for(Vertex vertex=targetVertex;vertex!=null;vertex=vertex.getPredecessor()){
                path.add(vertex);
            }

            Collections.reverse(path);
            return path;
        }

    }

Now, in Main I am trying something like this:

public static void main(String[] args) throws IOException {
{
int i, j;
 DjikstraShortestPath shortestPath = new DjikstraShortestPath();
        shortestPath.computeShortestPaths(vertex[0]); // setting the source to vertex[0]
        for(i=0; i<cities.size(); i++)
        {
            System.out.println("from"+vertex[0]+"to"+vertex[i]+"the distance is" + vertex[i].getDistance());
            System.out.println("Path: "+ shortestPath.getShortestPathTo(vertex[i]));
        }
shortestPath.computeShortestPaths(vertex[1]); //changing the source
for(i=0; i<cities.size(); i++)
        {
            System.out.println("from"+vertex[1]+"to"+vertex[i]+"the distance is" + vertex[i].getDistance());
            System.out.println("Path: "+ shortestPath.getShortestPathTo(vertex[i]));
        }
}

The problem that I am facing is that the intial source (inital city) vertex[0] when set produces the right result:

for example:

from A to A the distance is 0.0 //A is the main source in this case vertex[0]
path: A

from A to F the distance is 13.5
path: A D C B F

Now when I switch the source to vertex[1]

from B to A the distance is 0.0 //wrong because it uses the data from the previous (vertex[0])
path: A //this is wrong too

from B to F the distance is 13.5
path: A D C B F //uses the previous info from vertex[0] even though the source is changed to vertex[1]

Tried changing the function getShortestPathTo function in DijkstraShortestPath.java to this

public void getShortestPathTo(Vertex targetVertex){
            List<Vertex> path = new ArrayList<>();

            for(Vertex vertex=targetVertex;vertex!=null;vertex=vertex.getPredecessor()){
                path.add(vertex);
            }
            Collections.reverse(path);
            for(int i = 0; i<path.size(); i++)
            {
                System.out.println(path.get(i).getName());
            }
            path.clear();

        }


    }

Made all of the vertices unvisited and now I am facing an "Out of Memory" problem. There is a heap memory problem, I've literally tried everything.

Any help would be appreciated.

Keep safe, and stay at home people!

3 Answers

During the 1st call of computeShortestPaths, you write in all visited vertices that they are visited, and their distance to the source.

You do not reset this information before calling computeShortestPaths, so the vertices retain their distance and visited status (the if(!v.isVisited()) makes sure you do not update anything for nodes that were already visited in the first call).

So you need to clear all the information in the Vertex objects between the two calls, or (better) refactor your code so that this infomation is stored in the DjikstraShortestPath object rather than the vertices, and reset each time you call computeShortestPaths.

You need to initialize all your distances to infinity at the start of the algorithm. The distance kept in each vertex is a "shortest distance seen so far", so if you leave A's distance at 0 from the first run of your algorithm, the second run is going to assume that there is a shorter path to A and it has length 0. Similarly for visited.

See also steps 1 and 2 of the algorithm description on Wikipedia:

  1. Mark all nodes unvisited. [...]
  2. Assign to every node a tentative distance value: set it to zero for our initial node and to infinity for all other nodes. [...]

It works the first time because distance is initialized to Double.MAX_VALUE and visited to false. So before running the algorithm again, you need to reset those:

for(i=0; i<vertex.size; i++)
{
    vertex[i].setDistance(Double.MAX_VALUE);
    vertex[i].setVisited(false);
}

I too have faced the same issue (if I am relating correctly with yours) - Every thing is fine until the our origin vertex is the vertex where we can reach to all other vertex in the directed graph. The problem begins when we chose a origin vertex where we can not reach to all other vertices in same directed graph. ex -

            2      4        1
        A---->B--------->C----->D
        | \              |  \   |
      7 |  \9          13|  3\  |6
        |   \            |    \ |
        v    v           v     vv
        E---->F--------->G----->H
          1      8         13

Above, if we chose vertex A which can reaches to all other vertex i.e. B, C, D, E, F G & H - our code mostly work fine. But if we chose vertex C from where we can reach only to D, G & H above. The problem will start as when we extract Item for other un-reachable vertices B, C, E & F as a min item from our priority QUEUE to put them in final shortest path set/list. These items will have unrealistic distance in the shortest path set/list as they are not reachable from C. Further when we trace this shortest path set/list for origin vertex C to other vertices to print the shortest path, then we will get wrong information as unreachable vertices are also part of our final shortest path set/list.

So the solution is to restrict the entry of an item in final set/list extracted from our priority queue if that item have un-realistic distance. I have illustrated trough the code as below -

Check code under below comment line which restrict any un-reachable vertex as if distance is unrealistic to our final path list. //Restrict entry of Items having unrealistic path distances

package com.company.graph;

import java.util.*;

public class ShortestPath_Dijkstra {

    public static void main(String...args){

        String directedGraph =
                "\n\n            2      4        1\n" +
                "        A---->B--------->C----->D\n" +
                "        | \\              |  \\   |\n" +
                "      7 |  \\9          13|  3\\  |6\n" +
                "        |   \\            |    \\ |\n" +
                "        v    v           v     vv\n" +
                "        E---->F--------->G----->H\n" +
                "          1      8         13" ;



        // We store number instated of letters since in real world every vertex may have full qualified name ex - "LasVegas" instead of just "A"
        Map<Integer,String> vertices = new HashMap<>();
        vertices.put(0,"A");
        vertices.put(1,"B");
        vertices.put(2,"C");
        vertices.put(3,"D");
        vertices.put(4,"E");
        vertices.put(5,"F");
        vertices.put(6,"G");
        vertices.put(7,"H");

        Map<Edge, Integer> edges = new HashMap<>();

        //Implemented edges as a Map where for each entry -  key is a vertex and value is List containing edges i.e. all connecting vertices along with the weight !!
        Map<Integer, List<Edge>> verticesEdges = new HashMap<>();
        verticesEdges.put(0, new LinkedList<>(List.of(new Edge(1,2), new Edge(4,7),new Edge(5,9) )));
        verticesEdges.put(1, new LinkedList<>(List.of(new Edge(2,4))));
        verticesEdges.put(2, new LinkedList<>(List.of(new Edge(3,1),new Edge(6,13), new Edge(7,3))));
        verticesEdges.put(3, new LinkedList<>(List.of(new Edge(7,6))));
        verticesEdges.put(4, new LinkedList<>(List.of(new Edge(5,1) )));
        verticesEdges.put(5, new LinkedList<>(List.of(new Edge(6,8) )));
        verticesEdges.put(6, new LinkedList<>(List.of(new Edge(7,13))));
        verticesEdges.put(7, new LinkedList<>());


        Integer origin = 2; // alias C

        Map<Integer, Item> pathMap = getShortestPathMap(origin, vertices, verticesEdges);

        displayShortestPaths(directedGraph, origin, pathMap, vertices);

    }



    //Dijkstra function
    static Map<Integer, Item> getShortestPathMap(Integer origin, Map<Integer,String> vertices, Map<Integer, List<Edge>> verticesEdges){

        Map<Integer, Item> pathMap = new HashMap<>();

        PriorityQueue<Item> queue = new PriorityQueue<>();
        //Initialization of queue.
        vertices.keySet().forEach(v -> {
            if(v.equals(origin)){
                queue.add(new Item(v, 0, null));
            }else {
                queue.add(new Item(v));
            }
        });

        while(!queue.isEmpty()){

            Item currItem = queue.poll();

            //Restrict entry of Items having unrealistic path distances
            if(currItem.getDistance() != Integer.MAX_VALUE && currItem.getDistance() >= 0){
                pathMap.put(currItem.vertex, currItem);
            }

            verticesEdges.get(currItem.getVertex()).forEach(edge -> {
                //Get item in queue corresponding to vertex of this edge
                Item connItem = new Item(edge.getV());
                Iterator<Item> iterator = queue.iterator();
                boolean found = false;
                while(iterator.hasNext()){
                    Item inQueue = iterator.next();
                    if(inQueue.equals(connItem)){
                        connItem = inQueue;
                        found = true;
                        break;
                    }
                }
                //Update this connection Item distance if more than sum distance of current vertex and connecting edge weight. And also parent as current vertex.
                if(found && connItem.getDistance() > currItem.getDistance() + edge.getW()){
                    queue.remove(connItem);
                    queue.add(new Item(connItem.getVertex(), currItem.getDistance() + edge.getW(), currItem.getVertex()));
                }
            });
        }

        return pathMap;
    }

    //Display  function
    static void displayShortestPaths(String directedGraph, Integer origin,  Map<Integer, Item> pathMap, Map<Integer,String> vertices){

        System.out.println("For a directed Graph - " +  directedGraph );
        System.out.format("%nShortest Paths to all vertices starting from %S - %n", vertices.get(origin));

        vertices.keySet().forEach(v ->{
            if(pathMap.get(v)!=null){
                System.out.format("%n Shortest path(distance) from %S --> %S is %S", vertices.get(origin), vertices.get(v), pathMap.get(v).getDistance());
                System.out.format(" via vertices : ");

                Stack<String> path = new Stack<>();
                path.push(vertices.get(v));
                while(pathMap.get(v).getParent() != null){
                    v = pathMap.get(v).getParent();
                    path.push(vertices.get(v));
                }
                System.out.format("%S", path.pop());
                while (!path.empty()){
                    System.out.format("-->%S", path.pop());
                }
            }
        });
    }


    // Below class are Data Structures to store and process graph
    static class Edge {

        Integer v;   //Connecting Vertex
        Integer w;   //weight Of edge

        Edge(int v, int w) {
            this.v = v;
            this.w = w;
        }
        int getV() { return v; }
        int getW() { return w; }
    }


    static class Item implements Comparable<Item>{

        Integer vertex;
        Integer distance = Integer.MAX_VALUE;
        Integer parent = null;

        Item(Integer vertex) {
            this.vertex = vertex;
        }

        Item(Integer vertex, Integer distance, Integer parent) {
            this.vertex = vertex;
            this.distance = distance;
            this.parent = parent;
        }

        Integer getVertex() { return vertex; }
        Integer getDistance() { return distance; }

        Integer getParent() { return parent; }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Item item = (Item) o;
            return vertex.equals(item.vertex);
        }

        @Override
        public int hashCode() {
            return Objects.hash(vertex);
        }

        @Override
        public int compareTo(Item item) {
            return this.distance - item.distance;
        }
    }

}

Let we run the above code, I suppose to see only shortest distances to the reachable vertices from C not any un-realistic(un-reachable) ones -

For a directed Graph - 

            2      4        1
        A---->B--------->C----->D
        | \              |  \   |
      7 |  \9          13|  3\  |6
        |   \            |    \ |
        v    v           v     vv
        E---->F--------->G----->H
          1      8         13

Shortest Paths to all vertices starting from C - 

 Shortest path(distance) from C --> C is 0 via vertices : C
 Shortest path(distance) from C --> D is 1 via vertices : C-->D
 Shortest path(distance) from C --> G is 13 via vertices : C-->G
 Shortest path(distance) from C --> H is 3 via vertices : C-->H
Process finished with exit code 0

Let we also check that "if condition" to restrict un-realistic entries has not caused any negative impact for a case where vertex A as origin (i.e. all other vertices are reachable in the graph)- for that we need to do 1 line change to tell the origin vertex is now A,

Integer origin = 0; // alias A
For a directed Graph - 

            2      4        1
        A---->B--------->C----->D
        | \              |  \   |
      7 |  \9          13|  3\  |6
        |   \            |    \ |
        v    v           v     vv
        E---->F--------->G----->H
          1      8         13

Shortest Paths to all vertices starting from A - 

 Shortest path(distance) from A --> A is 0 via vertices : A
 Shortest path(distance) from A --> B is 2 via vertices : A-->B
 Shortest path(distance) from A --> C is 6 via vertices : A-->B-->C
 Shortest path(distance) from A --> D is 7 via vertices : A-->B-->C-->D
 Shortest path(distance) from A --> E is 7 via vertices : A-->E
 Shortest path(distance) from A --> F is 8 via vertices : A-->E-->F
 Shortest path(distance) from A --> G is 16 via vertices : A-->E-->F-->G
 Shortest path(distance) from A --> H is 9 via vertices : A-->B-->C-->H
Process finished with exit code 0
Related