All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graphstream.algorithm.APSP Maven / Gradle / Ivy

/*
 * Copyright 2006 - 2013
 *     Stefan Balev     
 *     Julien Baudry    
 *     Antoine Dutot    
 *     Yoann Pigné      
 *     Guilhelm Savin   
 * 
 * This file is part of GraphStream .
 * 
 * GraphStream is a library whose purpose is to handle static or dynamic
 * graph, create them from scratch, file or any source and display them.
 * 
 * This program is free software distributed under the terms of two licenses, the
 * CeCILL-C license that fits European law, and the GNU Lesser General Public
 * License. You can  use, modify and/ or redistribute the software under the terms
 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
 * URL  or under the terms of the GNU LGPL as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * This program 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 program.  If not, see .
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
 */
package org.graphstream.algorithm;

import java.util.*;

import org.graphstream.graph.Edge;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.Path;
import org.graphstream.stream.SinkAdapter;

/**
 * All-pair shortest paths lengths.
 * 
 * 

* This class implements the Floyd-Warshall all pair shortest path algorithm * where the shortest path from any node to any destination in a given weighted * graph (with positive or negative edge weights) is performed. *

*

* The computational complexity is O(n^3), this may seems a very large, however * this algorithm may perform better than running several Dijkstra on all node * pairs of the graph (that would be of complexity O(n^2 log(n))) when the graph * becomes dense. *

*

* Note that all the possible paths are not explicitly computed and stored. * Instead, the weight is computed and a data structure similar to network * routing tables is created directly on the graph. This allows a linear * reconstruction of the wanted paths, on demand, minimizing the memory * consumption. *

*

* For each node of the graph, a {@link org.graphstream.algorithm.APSP.APSPInfo} attribute is stored. The name of * this attribute is {@link org.graphstream.algorithm.APSP.APSPInfo#ATTRIBUTE_NAME}. *

*

Usage

*

* The implementation of this algorithm is made with two main classes that * reflect the two main steps of the algorithm that are: *

*
    *
  1. compute pairwise weights for all nodes;
  2. *
  3. retrieve actual paths from some given sources to some given destinations. *
  4. *
* *

* For the first step (the real shortest path computation) you need to create an * APSP object with 3 parameters: *

* *
    *
  • a reference to the graph to be computed;
  • *
  • a string that indicates the name of the attribute to consider for the * weighting;
  • *
  • a boolean that indicates whether the computation considers edges * direction or not.
  • *
* *

* Those 3 parameters can be set in a ran in the constructor * {@link #APSP(Graph,String,boolean)} or by using separated setters (see example * below). *

*

* Then the actual computation takes place by calling the {@link #compute()} method * which is implemented from the "Algorithm" interface. This method actually * does the computation. *

*

* Secondly, when the weights are computed, one can retrieve paths with the help * of another class: "APSPInfo". Such object are stored in each node and hold * routing tables that can help rebuild shortest paths. *

*

* Retrieving an "APSPInfo" instance from a node is done for instance for a * node of id "F", like this:: *

* *
 * APSPInfo info = graph.getNode("F").getAttribute(APSPInfo.ATTRIBUTE_NAME);
 * 
*

* then the shortest path from a "F" to another node (say "A") is given by:: *

* *
 * info.getShortestPathTo("A")
 * 
* * *

Example

* *
 * import java.io.ByteArrayInputStream;
 * import java.io.IOException;
 * 
 * import org.graphstream.algorithm.APSP;
 * import org.graphstream.algorithm.APSP.APSPInfo;
 * import org.graphstream.graph.Graph;
 * import org.graphstream.graph.implementations.DefaultGraph;
 * import org.graphstream.stream.file.FileSourceDGS;
 * 
 * public class APSPTest {
 * 
 * //     B-(1)-C
 * //    /       \
 * //  (1)       (10)
 * //  /           \
 * // A             F
 * //  \           /
 * //  (1)       (1)
 * //    \       /
 * //     D-(1)-E
 * 
 * 	static String my_graph = "DGS004\n" 
 * 			+ "my 0 0\n" 
 * 			+ "an A \n" 
 * 			+ "an B \n"
 * 			+ "an C \n" 
 * 			+ "an D \n" 
 * 			+ "an E \n" 
 * 			+ "an F \n"
 * 			+ "ae AB A B weight:1 \n" 
 * 			+ "ae AD A D weight:1 \n"
 * 			+ "ae BC B C weight:1 \n" 
 * 			+ "ae CF C F weight:10 \n"
 * 			+ "ae DE D E weight:1 \n" 
 * 			+ "ae EF E F weight:1 \n";
 * 
 * 	public static void main(String[] args) throws IOException {
 * 		Graph graph = new DefaultGraph("APSP Test");
 * 		ByteArrayInputStream bs = new ByteArrayInputStream(my_graph.getBytes());
 * 
 * 		FileSourceDGS source = new FileSourceDGS();
 * 		source.addSink(graph);
 * 		source.readAll(bs);
 * 
 * 		APSP apsp = new APSP();
 * 		apsp.init(graph); // registering apsp as a sink for the graph
 * 		apsp.setDirected(false); // undirected graph
 * 		apsp.setWeightAttributeName("weight"); // ensure that the attribute name
 * 												// used is "weight"
 * 		apsp.compute(); // the method that actually computes shortest paths
 * 
 * 		APSPInfo info = graph.getNode("F")
 * 				.getAttribute(APSPInfo.ATTRIBUTE_NAME);
 * 		System.out.println(info.getShortestPathTo("A"));
 * 	}
 * }
 * 
* *

Other Features

* *

Digraphs

*

* This algorithm can use directed graphs and only compute paths according to * this direction. You can choose to ignore edge orientation by calling * {@link #setDirected(boolean)} method with "false" as value (or use the * appropriate constructor). *

* * *

Shortest Paths with weighted edges *

* You can also specify that edges have "weights" or "importance" that value * them. You store these values as attributes on the edges. The default name for * these attributes is "weight" but you can specify it using the * {@link #setWeightAttributeName(String)} method (or by using the appropriate * constructor). The weight attribute must contain an object that implements * java.lang.Number. *

* *

How shortest paths are stored in the graph?

*

* All the shortest paths are not literally stored in the graph because it would * require to much memory to do so. Instead, only useful data, allowing the fast * reconstruction of any path, is stored. The storage approach is alike network * routing tables where each node maintains a list of all possible targets * linked with the next hop neighbor to go through. *

*

* Technically, on each node, for each target, we only store the target node * name and if the path is made of more than one edge, one "pass-by" node. As * all shortest path that is made of more than one edge is necessarily made of * two other shortest paths, it is easy to reconstruct a shortest path between * two arbitrary nodes knowing only a pass-by node. This approach still stores a * lot of data on the graph, however far less than if we stored complete paths. *

* * @complexity O(n^3) with n the number of nodes. * * @reference Floyd, Robert W. "Algorithm 97: Shortest Path". Communications of * the ACM 5 (6): 345. doi:10.1145/367766.368168. 1962. * @reference Warshall, Stephen. "A theorem on Boolean matrices". Journal of the * ACM 9 (1): 11–12. doi:10.1145/321105.321107. 1962. * */ public class APSP extends SinkAdapter implements Algorithm { // Attribute /** * The graph to use. */ protected Graph graph; /** * Does the graph changed between two calls to {@link #compute()}?. */ protected boolean graphChanged = false; /** * If false, do not take edge orientation into account. */ protected boolean directed = true; /** * Name of the attribute on each edge indicating the weight of the edge. * This attribute must contain a descendant of Number. */ protected String weightAttributeName; protected Progress progress = null; // Construction public APSP() { this(null); } /** * New APSP algorithm working on the given graph. The edge weight attribute * name by default is "weight" and edge orientation is taken into account. * * @param graph * The graph to use. */ public APSP(Graph graph) { this(graph, "weight", true); } /** * New APSP algorithm working on the given graph. To fetch edges importance, * the algorithm use the given string as attribute name for edge weights. * Weights must be a descendant of Number. * * @param graph * The graph to use. * @param weightAttributeName * The edge weight attribute name. * @param directed * If false, edge orientation is ignored. */ public APSP(Graph graph, String weightAttributeName, boolean directed) { this.graph = graph; this.weightAttributeName = weightAttributeName; this.directed = directed; init(graph); } // Access /** * True if the algorithm must take edge orientation into account. * * @return True if directed. */ public boolean isDirected() { return directed; } /** * The name of the attribute to use for retrieving edge weights. * * @return An attribute name. */ public String getWeightAttributeName() { return weightAttributeName; } /** * Access to the working graph. * * @return graph being used */ public Graph getGraph() { return graph; } // Commands /** * Choose to use or ignore edge orientation. * * @param on * If true edge orientation is used.b */ public void setDirected(boolean on) { directed = on; } /** * Specify an interface to call in order to indicate the algorithm progress. * Pass null to remove the progress indicator. The progress indicator will * be called regularly to indicate the computation progress. */ public void registerProgressIndicator(Progress progress) { this.progress = progress; } /** * Choose the name of the attribute used to retrieve edge weights. Edge * weights attribute must contain a value that inherit Number. * * @param name * The attribute name. */ public void setWeightAttributeName(String name) { weightAttributeName = name; } /** * @see Algorithm#init(Graph) */ public void init(Graph graph) { if (this.graph != null) this.graph.removeSink(this); this.graph = graph; if (this.graph != null){ graphChanged = true; this.graph.addSink(this); } } /** * Run the APSP computation. When finished, the graph is equipped with * specific attributes of type * {@link org.graphstream.algorithm.APSP.APSPInfo}. These attributes contain * a map of length toward each other attainable node. The attribute name is * given by {@link org.graphstream.algorithm.APSP.APSPInfo#ATTRIBUTE_NAME}. * * @complexity O(n^3) where n is the number of nodes in the graph. */ public void compute() { if (graphChanged) { // Make a list of all nodes, and equip them with APSP informations. // The APSPInfo constructor add in each info item all the paths from // the node to all its neighbour. It set the distance to 1 if there // are no weights on edges. ArrayList nodeList = new ArrayList(); for (Node node : graph) { node.addAttribute(APSPInfo.ATTRIBUTE_NAME, new APSPInfo(node, weightAttributeName, directed)); nodeList.add(node); } // The Floyd-Warshall algorithm. You can easily see it is in O(n^3).. // int z = 0; double prog = 0; double max = nodeList.size(); max *= max; for (Node k : nodeList) { for (Node i : nodeList) { for (Node j : nodeList) { APSPInfo I = (APSPInfo) i.getAttribute( APSPInfo.ATTRIBUTE_NAME, APSPInfo.class); APSPInfo J = (APSPInfo) j.getAttribute( APSPInfo.ATTRIBUTE_NAME, APSPInfo.class); APSPInfo K = (APSPInfo) k.getAttribute( APSPInfo.ATTRIBUTE_NAME, APSPInfo.class); double Dij = I.getLengthTo(J.source.getId()); double Dik = I.getLengthTo(K.source.getId()); double Dkj = K.getLengthTo(J.source.getId()); // Take into account non-existing paths. if (Dik >= 0 && Dkj >= 0) { double sum = Dik + Dkj; if (Dij >= 0) { if (sum < Dij) { I.setLengthTo(J, sum, K); } } else { I.setLengthTo(J, sum, K); } } } if (progress != null) progress.progress(prog / max); prog += 1; } // z++; // System.err.printf( "%3.2f%%%n", (z/((double)n))*100 ); } } graphChanged = false; } /** * Information stored on each node of the graph giving the length of the * shortest paths toward each other node. */ public static class APSPInfo { public static final String ATTRIBUTE_NAME = "APSPInfo"; /** * The start node name. This information is stored inside this node. */ public Node source; /** * Maximum number of hops to attain another node in the graph from the * "from" node. * XXX this is the maximum value seen during compute not the maximum shortest path XXX */ public double maxLength = Double.MIN_VALUE; /** * Minimum number of hops to attain another node in the graph from the * "from" node. * XXX this is the minimum value seen during compute not the minimum shortest path XXX */ public double minLength = Double.MAX_VALUE; /** * Shortest paths toward all other accessible nodes. */ public HashMap targets = new HashMap(); /** * Create the new information and put in it all the paths between this * node and all its direct neighbours. * * @param node * The node to start from. * @param weightAttributeName * The key used to retrieve the weight attributes of edges. * This attribute but store a value that inherit Number. * @param directed * If false, the edge orientation is not taken into account. */ public APSPInfo(Node node, String weightAttributeName, boolean directed) { double weight = 1; Iterable edges = node.getLeavingEdgeSet(); source = node; if (!directed) edges = node.getEdgeSet(); for (Edge edge : edges) { Node other = edge.getOpposite(node); if (edge.hasAttribute(weightAttributeName)) weight = edge.getNumber(weightAttributeName); targets.put(other.getId(), new TargetPath(other, weight, null)); } } /** * The node represented by this APSP information. * * @return A node identifier. */ public String getNodeId() { return source.getId(); } /** * Minimum distance between this node and another. This returns -1 if * there is no path stored yet between these two nodes. * * @param other * The other node identifier. * @return The distance or -1 if no path is stored yet between the two * nodes. */ public double getLengthTo(String other) { if (targets.containsKey(other)) return targets.get(other).distance; return -1; } /** * The minimum distance between this node and another. * XXX this is the minimum value seen during compute not the minimum shortest path XXX * * @return A distance. */ public double getMinimumLength() { return minLength; } /** * The maximum distance between this node and another. * XXX this is the maximum value seen during compute not the maximum shortest path XXX * * @return A distance. */ public double getMaximumLength() { return maxLength; } /** * Add or change the length between this node and another and update the * minimum and maximum lengths seen so far. * * @param other * The other node APSP info. * @param length * The new minimum path lengths between these nodes. */ public void setLengthTo(APSPInfo other, double length, APSPInfo passBy) { targets.put(other.source.getId(), new TargetPath(other.source, length, passBy)); if (length < minLength) minLength = length; if (length > maxLength) maxLength = length; } public Path getShortestPathTo(String other) { TargetPath tpath = targets.get(other); // XXX Probably a bug here in the Path class usage. // TODO update this to create an edge path to be compatible with // multi-graphs. if (tpath != null) { Path path = new Path(); // XXX use the Path object directly. ArrayList nodePath = new ArrayList(); nodePath.add(source); nodePath.add(tpath.target); // Recursively build the path between the source and target node // by exploring pass-by nodes. expandPath(1, this, tpath, nodePath); // Build a Path object. for (int i = 0; i < nodePath.size() - 1; ++i) { // XXX XXX complicated ? path.add( nodePath.get(i), nodePath.get(i).getEdgeToward( nodePath.get(i + 1).getId())); } return path; } return null; } protected int expandPath(int pos, APSPInfo source, TargetPath path, ArrayList nodePath) { // result = will contain the expanded path. // source = A. // path.passBy = X. // path.target = B. // pos = position of insertion of X inside result. if (path.passBy != null) { // We want to insert X between A and B. nodePath.add(pos, path.passBy.source); // We build paths between A and X and between X and B. TargetPath path1 = source.targets.get(path.passBy.source .getId()); // path from A -> X TargetPath path2 = path.passBy.targets.get(path.target.getId()); // path from X -> B // Now we recurse the path expansion. int added1 = expandPath(pos, source, path1, nodePath); int added2 = expandPath(pos + 1 + added1, path.passBy, path2, nodePath); // Return the number of elements added at pos. return added1 + added2 + 1; } else { // These is no more intermediary node X, stop the recursion. return 0; } } } /** * Description of a path to a target node. * *

* This class is made to be used by the APSPInfo class, which references a * source node. This class describes a target node, the length of the * shortest path to it and, if the path is made of more than only one edge, * an intermediary node (pass-by), used to reconstruct recursively the * shortest path. *

* *

* This representation avoids to store each node of each shortest path, * since this would consume a too large memory area. This way, a shortest * path is stored at constant size (this is possible since we computed all * the shortest paths and, knowing that a path of more than one edge is * always made of the sum of two shortest paths, and knowing only one * "pass-by" node in the shortest path, it is possible to rebuild it). *

*/ public static class TargetPath { /** * A distant other node. */ public Node target; /** * The distance to this other node. */ public double distance; /** * An intermediary other node on the minimum path to the other node. * Used to reconstruct the path between two nodes. */ public APSPInfo passBy; public TargetPath(Node other, double distance, APSPInfo passBy) { this.target = other; this.distance = distance; this.passBy = passBy; } } // Sink implementation @Override public void nodeAdded(String graphId, long timeId, String nodeId) { graphChanged = true; } @Override public void nodeRemoved(String graphId, long timeId, String nodeId) { graphChanged = true; } @Override public void edgeAdded(String graphId, long timeId, String edgeId, String fromNodeId, String toNodeId, boolean directed) { graphChanged = true; } @Override public void edgeRemoved(String graphId, long timeId, String edgeId) { graphChanged = true; } @Override public void graphCleared(String graphId, long timeId) { graphChanged = true; } @Override public void edgeAttributeAdded(String graphId, long timeId, String edgeId, String attribute, Object value) { if (attribute.equals(weightAttributeName)) { graphChanged = true; } } @Override public void edgeAttributeChanged(String graphId, long timeId, String edgeId, String attribute, Object oldValue, Object value) { if (attribute.equals(weightAttributeName)) { graphChanged = true; } } /** * Interface allowing to be notified of the algorithm progress. */ public interface Progress { /** * Progress of the algorithm. * * @param percent * a value between 0 and 1, 0 meaning 0% and 1 meaning 100%. */ void progress(double percent); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy