import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; /** * An implementation of Dijkstra's shortest path algorithm. It computes the shortest path (in distance) * to all nodes in the map. The output of the algorithm is the shortest distance from the start node * to every other node, and the shortest path from the start node to every other. *
* Upon calling * {@link #execute(Node, Node)}, * the results of the algorithm are made available by calling * {@link #getPredecessor(Node)} * and * {@link #getShortestDistance(Node)}. * * To get the shortest path between the node destination and * the source node after running the algorithm, one would do: *
* List l = new ArrayList();
*
* for (Node node = destination; node != null; node = engine.getPredecessor(node))
* {
* l.add(node);
* }
*
* Collections.reverse(l);
*
* return l;
*
*
* @see #execute(Node, Node)
*
* @author Renaud Waldura <renaud+tw@waldura.com>
* @version $Id: DijkstraEngine.java,v 1.4 2003/03/03 01:26:22 renaud Exp $
*/
public class DijkstraEngine
{
/**
* Infinity value for distances.
*/
public static final int INFINITE_DISTANCE = Integer.MAX_VALUE;
/**
* This comparator orders nodes according to their shortest distances,
* in ascending fashion. If two nodes have the same shortest distance,
* we compare the nodes themselves.
*/
private final Comparator shortestDistanceComparator = new Comparator()
{
public int compare(Object left, Object right)
{
assert left instanceof Node && right instanceof Node : "invalid comparison";
return compare((Node) left, (Node) right);
}
private int compare(Node left, Node right)
{
// note that this trick doesn't work for huge distances, close to Integer.MAX_VALUE
int result = getShortestDistance(left) - getShortestDistance(right);
return (result == 0) ? left.compareTo(right) : result;
}
};
/**
* The graph.
*/
private final RoutesMap map;
/**
* The working set of nodes, kept ordered by shortest distance.
*/
private final SortedSet unsettledNodes = new TreeSet(shortestDistanceComparator);
/**
* The set of nodes for which the shortest distance to the source
* has been found.
*/
private final Set settledNodes = new HashSet();
/**
* The currently known shortest distance for all nodes.
*/
private final Map shortestDistances = new HashMap();
/**
* Predecessors list: maps a node to its predecessor in the spanning tree of
* shortest paths.
*/
private final Map predecessors = new HashMap();
/**
* Constructor.
*/
public DijkstraEngine(RoutesMap map)
{
this.map = map;
}
/**
* Initialize all data structures used by the algorithm.
*
* @param start the source node
*/
private void init(Node start)
{
settledNodes.clear();
unsettledNodes.clear();
shortestDistances.clear();
predecessors.clear();
// add source
setShortestDistance(start, 0);
unsettledNodes.add(start);
}
/**
* Run Dijkstra's shortest path algorithm on the map.
* The results of the algorithm are available through
* {@link #getPredecessor(Node)}
* and
* {@link #getShortestDistance(Node)}
* upon completion of this method.
*
* @param start the starting node
* @param destination the destination node. If this argument is null, the algorithm is
* run on the entire graph, instead of being stopped as soon as the destination is reached.
*/
public void execute(Node start, Node destination)
{
init(start);
// the current node
Node u;
// extract the node with the shortest distance
while ((u = extractMin()) != null)
{
assert !isSettled(u);
// destination reached, stop
if (u == destination) break;
markSettled(u);
relaxNeighbors(u);
}
}
/**
* Extract the node with the currently shortest distance, and remove it from
* the priority queue.
*
* @return the minimum node, or null if the queue is empty.
*/
private Node extractMin()
{
if (unsettledNodes.isEmpty()) return null;
Node min = (Node) unsettledNodes.first();
unsettledNodes.remove(min);
return min;
}
/**
* Compute new shortest distance for neighboring nodes and update if a better
* distance is found.
*
* @param u the node
*/
private void relaxNeighbors(Node u)
{
for (Iterator i = map.getDestinations(u).iterator(); i.hasNext(); )
{
Node v = (Node) i.next();
// skip node already settled
if (isSettled(v)) continue;
if (getShortestDistance(v) > getShortestDistance(u) + map.getDistance(u, v))
{
// assign new shortest distance and mark unsettled
setShortestDistance(v, getShortestDistance(u) + map.getDistance(u, v));
// assign predecessor in shortest path
setPredecessor(v, u);
}
}
}
/**
* Mark a node as settled.
*
* @param u the node
*/
private void markSettled(Node u)
{
settledNodes.add(u);
}
/**
* Test a node.
*
* @param v the node to consider
*
* @return whether the node is settled, ie. its shortest distance
* has been found.
*/
private boolean isSettled(Node v)
{
return settledNodes.contains(v);
}
/**
* @return the shortest distance from the source to the given node, or
* {@link DijkstraEngine#INFINITE_DISTANCE} if there is no route to the destination.
*/
public int getShortestDistance(Node node)
{
Integer d = (Integer) shortestDistances.get(node);
return (d == null) ? INFINITE_DISTANCE : d.intValue();
}
/**
* Set the new shortest distance for the given node,
* and re-balance the set according to new shortest distances.
*
* @param node the node to set
* @param distance new shortest distance value
*/
private void setShortestDistance(Node node, int distance)
{
// this crucial step ensure no duplicates will be created in the queue
// when an existing unsettled node is updated with a new shortest distance
unsettledNodes.remove(node);
shortestDistances.put(node, new Integer(distance));
// re-balance the sorted set according to the new shortest distance found
// (see the comparator the set was initialized with)
unsettledNodes.add(node);
}
/**
* @return the node leading to the given node on the shortest path, or
* null if there is no route to the destination.
*/
public Node getPredecessor(Node node)
{
return (Node) predecessors.get(node);
}
private void setPredecessor(Node a, Node b)
{
predecessors.put(a, b);
}
}