Shortest Path with Dijkstra
Asked Answered
N

1

6

I using this exact code for this. I modified it a little. So far I added a start and end node index to the calculateShortestDistances() method. Also the path ArrayList for collecting the path node indexes. Also: new to Java...

How do I collect the indexes of nodes in the path ArrayList?

I just can't come up with the solution on a level that I am not even positive this code could do what I want. I only have intuition on my side and little time.

What I tried:

  • Adding the nextNode value to the list then removing it if it was not a shorter distance.
  • Adding the neighbourIndex to the list then removing it if it was not a shorter distance.
  • I made a Path.java with ArrayList but that was went nowhere (it was a class with a public variable named path) but it went nowhere.

Main.java:

public class Main {
  public static void main(String[] args) {
    Edge[] edges = {
      new Edge(0, 2, 1), new Edge(0, 3, 4), new Edge(0, 4, 2),
      new Edge(0, 1, 3), new Edge(1, 3, 2), new Edge(1, 4, 3),
      new Edge(1, 5, 1), new Edge(2, 4, 1), new Edge(3, 5, 4),
      new Edge(4, 5, 2), new Edge(4, 6, 7), new Edge(4, 7, 2),
      new Edge(5, 6, 4), new Edge(6, 7, 5)
    };
    Graph g = new Graph(edges);
    g.calculateShortestDistances(4,6);
    g.printResult(); // let's try it !

    System.out.println(g.path);
  }
}

Graph.java:

This is the Graph.java file. Here I added a sAt and eAt variable, so I can tell it what path I am after. Also I created a public path ArrayList, where I intend to collect the path.

import java.util.ArrayList;
// now we must create graph object and implement dijkstra algorithm
public class Graph {
  private Node[] nodes;
  private int noOfNodes;
  private Edge[] edges;
  private int noOfEdges;

  private int sAt;
  private int eAt;

  public ArrayList<Integer> path = new ArrayList<>();

  public Graph(Edge[] edges) {
    this.edges = edges;
    // create all nodes ready to be updated with the edges
    this.noOfNodes = calculateNoOfNodes(edges);
    this.nodes = new Node[this.noOfNodes];
    for (int n = 0; n < this.noOfNodes; n++) {
      this.nodes[n] = new Node();
    }
    // add all the edges to the nodes, each edge added to two nodes (to and from)
    this.noOfEdges = edges.length;
    for (int edgeToAdd = 0; edgeToAdd < this.noOfEdges; edgeToAdd++) {
      this.nodes[edges[edgeToAdd].getFromNodeIndex()].getEdges().add(edges[edgeToAdd]);
      this.nodes[edges[edgeToAdd].getToNodeIndex()].getEdges().add(edges[edgeToAdd]);
    }
  }
  private int calculateNoOfNodes(Edge[] edges) {
    int noOfNodes = 0;
    for (Edge e : edges) {
      if (e.getToNodeIndex() > noOfNodes)
        noOfNodes = e.getToNodeIndex();
      if (e.getFromNodeIndex() > noOfNodes)
        noOfNodes = e.getFromNodeIndex();
    }
    noOfNodes++;
    return noOfNodes;
  }

  public void calculateShortestDistances(int startAt, int endAt) {

    // node 0 as source
    this.sAt = startAt;
    this.eAt = endAt;
    this.nodes[startAt].setDistanceFromSource(0);
    int nextNode = startAt;
    // visit every node
    for (int i = 0; i < this.nodes.length; i++) {
      // loop around the edges of current node
      ArrayList<Edge> currentNodeEdges = this.nodes[nextNode].getEdges();

      for (int joinedEdge = 0; joinedEdge < currentNodeEdges.size(); joinedEdge++) {

        int neighbourIndex = currentNodeEdges.get(joinedEdge).getNeighbourIndex(nextNode);
        // only if not visited

        if (!this.nodes[neighbourIndex].isVisited()) {
          int tentative = this.nodes[nextNode].getDistanceFromSource() + currentNodeEdges.get(joinedEdge).getLength();

          if (tentative < nodes[neighbourIndex].getDistanceFromSource()) {
            nodes[neighbourIndex].setDistanceFromSource(tentative);



          }
        }

      }
      // all neighbours checked so node visited
      nodes[nextNode].setVisited(true);
      // next node must be with shortest distance
      nextNode = getNodeShortestDistanced();
   }
  }
  // now we're going to implement this method in next part !
  private int getNodeShortestDistanced() {
    int storedNodeIndex = 0;
    int storedDist = Integer.MAX_VALUE;
    for (int i = 0; i < this.nodes.length; i++) {
      int currentDist = this.nodes[i].getDistanceFromSource();

      if (!this.nodes[i].isVisited() && currentDist < storedDist) {
        storedDist = currentDist;
        storedNodeIndex = i;

      } 
    }
    return storedNodeIndex;
  }
  // display result
  public void printResult() {
    String output = "Number of nodes = " + this.noOfNodes;
    output += "\nNumber of edges = " + this.noOfEdges;

    output += "\nDistance from "+sAt+" to "+eAt+":" + nodes[eAt].getDistanceFromSource();

    System.out.println(output);
  }
  public Node[] getNodes() {
    return nodes;
  }
  public int getNoOfNodes() {
    return noOfNodes;
  }
  public Edge[] getEdges() {
    return edges;
  }
  public int getNoOfEdges() {
    return noOfEdges;
  }
}

Addittionally here are the Edge.java and the Node.java classes.

Node.java:

import java.util.ArrayList;
public class Node {
  private int distanceFromSource = Integer.MAX_VALUE;
  private boolean visited;
  private ArrayList<Edge> edges = new ArrayList<Edge>(); // now we must create edges
  public int getDistanceFromSource() {
    return distanceFromSource;
  }
  public void setDistanceFromSource(int distanceFromSource) {
    this.distanceFromSource = distanceFromSource;
  }
  public boolean isVisited() {
    return visited;
  }
  public void setVisited(boolean visited) {
    this.visited = visited;
  }
  public ArrayList<Edge> getEdges() {
    return edges;
  }
  public void setEdges(ArrayList<Edge> edges) {
    this.edges = edges;
  }
}

Edge.java

public class Edge {
  private int fromNodeIndex;
  private int toNodeIndex;
  private int length;
  public Edge(int fromNodeIndex, int toNodeIndex, int length) {
    this.fromNodeIndex = fromNodeIndex;
    this.toNodeIndex = toNodeIndex;
    this.length = length;
  }
  public int getFromNodeIndex() {
    return fromNodeIndex;
  }
  public int getToNodeIndex() {
    return toNodeIndex;
  }
  public int getLength() {
    return length;
  }
  // determines the neighbouring node of a supplied node, based on the two nodes connected by this edge
  public int getNeighbourIndex(int nodeIndex) {
    if (this.fromNodeIndex == nodeIndex) {
      return this.toNodeIndex;
    } else {
      return this.fromNodeIndex;
   }
  }
}

I know it looks like a homework. Trust me it isn't. On the other hand I have not much time to finish it, that is why I do it at Sunday. Also I am aware how Dijkstra algorithm works, I understand the concept, I can do it on paper. But collecting the path is beyond me.

Northwestwards answered 4/8, 2019 at 15:11 Comment(4)
You could change your setDistanceFromSource to also accept the node where it came from. After thats it is a simple backtracking to the source, once you reached your destination.Ralina
In the Dijkstra algorithm i know, a node not only knows his distance from source, but also his predecessor. You initialise all nodes with predecessor = null and distance = infinity. When you visit a node, you calculate the distance from source via actual node for all his neighbours. If that distance is shorter than the stored distance, you replace it; that is what you do. But you have also to store the actual node as predecessor in that neighbour node. To get the path, you start with the end node and get a chain of predecessor nodes until you have the start node whose predecessor is null.Retouch
Thank you @ChristianH.Kuhn (and second). This helped a lot. I know it will be something very simple, as I said I had the intuition for it. But I focused too much on the Graph.java without thinking about the anatomy of the nodes and the graph it self.Futhark
Glad to help. Don’t forget to vote ;-)Retouch
N
1

Thanks for Christian H. Kuhn's and second's comments I managed to come up with the code.

I modified it as follows (I only put in the relevant parts)

Node.java Here I added a setPredecessor(Integer predecessor) and a getPredecessor() methods to set and get the value of the private variable predecessor (so I follow the original code's style too).

  [...]
  private int predecessor;

  [...]
  public int getPredecessor(){
    return predecessor;
  }
  public void setPredecessor(int predecessor){
    this.predecessor = predecessor;
  }
  [...]

Graph.java Here I created the calculatePath() and getPath() methods. calculatePath() does what the commenters told me to do. The getPath() returns the ArrayLists for others to use.

  [...]

  private int sAt;
  private int eAt;

  private ArrayList<Integer> path = new ArrayList<Integer>();

  [...]

  public void calculateShortestDistances(int startAt, int endAt) {

  [...]
          if (tentative < nodes[neighbourIndex].getDistanceFromSource()) {
            nodes[neighbourIndex].setDistanceFromSource(tentative);
            nodes[neighbourIndex].setPredecessor(nextNode);
          }

  [...]
  public void calculatePath(){
        int nodeNow = eAt;

        while(nodeNow != sAt){
            path.add(nodes[nodeNow].getPredecessor());
            nodeNow = nodes[nodeNow].getPredecessor();

        }

    }

    public ArrayList<Integer> getPath(){

        return path;

    }
    [...]

Main.java so here I can do this now:

[...]
Graph g = new Graph(edges);
g.calculateShortestDistances(5,8);
g.calculatePath();

String results =  "";

ArrayList<Integer> path = g.getPath();

System.out.println(path);
[...]

I know it shows the path backwards, but that is not a problem, as I can always reverse it. The point is: I not only have the the distance from node to node, but the path through nodes too. Thank you for the help.

Northwestwards answered 5/8, 2019 at 8:24 Comment(3)
I even would declare private Node predecessor; in Node.java.Retouch
Just had a look at old code of mine, where i used Dijkstra to optimize a game strategy (and failed for other but the shortest solutions for complexity reasons – and i thought chess was a complex game!). I never used any explicit graph. I had a list of edges. Starting with the start node, i created or updated all new neighbour nodes in a list sorted by known distance. The first element of this list was the next node to visit. A visited node was moved from that list to another (unsorted) until the end node was reached (solution) or the sorted list was empty (unsolvable). This should save memory.Retouch
@ChristianH.Kuhn I keep this in mind. So far it looks good, not too heavy on memory in my case but could get out of hand.Futhark

© 2022 - 2024 — McMap. All rights reserved.