From 19fc217b078572d0944e0992648765c4346e5069 Mon Sep 17 00:00:00 2001 From: mdb Date: Fri, 3 Dec 2004 00:51:03 +0000 Subject: [PATCH] A (fairly) general purpose implementation of Dijkstra's shortest path algorithm. git-svn-id: https://samskivert.googlecode.com/svn/trunk@1537 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../com/samskivert/util/ShortestPath.java | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 projects/samskivert/src/java/com/samskivert/util/ShortestPath.java diff --git a/projects/samskivert/src/java/com/samskivert/util/ShortestPath.java b/projects/samskivert/src/java/com/samskivert/util/ShortestPath.java new file mode 100644 index 00000000..40703c76 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/util/ShortestPath.java @@ -0,0 +1,142 @@ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001-2004 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +/** + * Implements Dijkstra's algorithm for finding the shortest path between + * two nodes in a weighted graph. The code assumes that the caller + * represents their nodes and edges as objects which can be compared and + * hashed (via {@link Object#equals} and {@link Object#hashCode}) other + * necessary information is obtained through a special interface {@link + * Graph} implemented by the caller to enumerate edges and compute + * weights. + */ +public class ShortestPath +{ + /** A caller must implement this interface to provide the information + * needed to define the graph and compute the shortest path. */ + public interface Graph + { + /** Enumerates all nodes in the graph. */ + public Iterator enumerateNodes (); + + /** Returns the list of the edges for the specified node. */ + public List getEdges (Object node); + + /** Returns the weight associated with the supplied edge in the + * direction established by the supplied starting node. */ + public int computeWeight (Object edge, Object start); + + /** Returns the node opposite the supplied node on the supplied edge. */ + public Object getOpposite (Object edge, Object node); + } + + /** + * Computes the shortest path between the specified starting and + * ending nodes using Dijkstra's algorithm. This implementation + * assumes that the graph is properly formed and may behave strangely + * or throw an exception if provided with an invalid graph. + * + * @return a list of the edges that must be followed to traverse from + * the starting node to the ending node. This list may be empty if the + * graph is improperly formed. + */ + public static List compute (Graph graph, Object start, Object end) + { + HashMap nodes = new HashMap(); + HashSet relaxed = new HashSet(); + SortableArrayList uptight = new SortableArrayList(); + + // initialize our searching info + for (Iterator iter = graph.enumerateNodes(); iter.hasNext(); ) { + NodeInfo info = new NodeInfo(); + info.node = iter.next(); + if (info.node == start) { + info.weightTo = 0; + } + uptight.add(info); + nodes.put(info.node, info); + } + uptight.sort(); + + // now execute the main part of the search + while (uptight.size() > 0) { + // remove the cheapest known node + NodeInfo info = (NodeInfo)uptight.remove(uptight.size()-1); + // make a note that it is now relaxed + relaxed.add(info.node); + // relax its uptight neighbors + List edges = graph.getEdges(info.node); + for (int ii = 0, ll = edges.size(); ii < ll; ii++) { + Object edge = edges.get(ii); + Object onode = graph.getOpposite(edge, info.node); + if (relaxed.contains(onode)) { + continue; + } + // if the path through this node to its neighbor is + // cheaper than the existing known shortest path, update + // the neighbor to reflect this new shorter path + NodeInfo oinfo = (NodeInfo)nodes.get(onode); + int weight = graph.computeWeight(edge, info.node); + if (oinfo.weightTo > info.weightTo + weight) { + oinfo.weightTo = info.weightTo + weight; + oinfo.edgeTo = edge; + } + } + // now resort the uptight list + uptight.sort(); + } + + // now trace the path from the final node back to the start + ArrayList path = new ArrayList(); + NodeInfo info = (NodeInfo)nodes.get(end); + while (info.edgeTo != null) { + path.add(0, info.edgeTo); + info = (NodeInfo)nodes.get( + graph.getOpposite(info.edgeTo, info.node)); + } + return path; + } + + /** Used to maintain information during the shortest path search. */ + protected static final class NodeInfo + implements Comparable + { + /** The node for which we're representing information. */ + public Object node; + + /** The cumulative weight to this node from the source. */ + public int weightTo = Integer.MAX_VALUE / 4; + + /** The edge followed to reach this node along the shortest path. */ + public Object edgeTo; + + /** We order ourselves by the cumulative weight to this node. */ + public int compareTo (Object o) + { + return ((NodeInfo)o).weightTo - weightTo; + } + } +}