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

org.jgrapht.graph.GraphWalk Maven / Gradle / Ivy

There is a newer version: 1.5.2
Show newest version
/*
 * (C) Copyright 2016-2020, by Joris Kinable and Contributors.
 *
 * JGraphT : a free Java graph-theory library
 *
 * See the CONTRIBUTORS.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the
 * GNU Lesser General Public License v2.1 or later
 * which is available at
 * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
 */
package org.jgrapht.graph;

import org.jgrapht.*;

import java.io.*;
import java.util.*;
import java.util.function.*;

/**
 * A walk in a graph is an alternating sequence of vertices and edges, starting and ending at a
 * vertex, in which each edge is adjacent in the sequence to its two endpoints. More precisely, a
 * walk is a connected sequence of vertices and edges in a graph $v_0, e_0, v_1, e_1, v_2, \dotso,
 * v_{k-1}, e_{k-1}, v_{k}$, such that for $1 \leq i \leq k$, the edge $e_i$ has endpoints $v_{i-1}$
 * and $v_i$. The class makes no assumptions with respect to the shape of the walk: edges may be
 * repeated, and the start and end point of the walk may be different.
 *
 * 

* See http://mathworld.wolfram.com/Walk.html * *

* GraphWalk is the default implementation of {@link GraphPath}. * *

* Two special cases exist: *

    *
  1. A singleton GraphWalk has an empty edge list (the length of the path equals 0), the vertex * list contains a single vertex v, and the start and end vertex equal v.
  2. *
  3. An empty Graphwalk has empty edge and vertex lists, and the start and end vertex are both * null.
  4. *
* *

* This class is implemented as a light-weight data structure; this class does not verify whether * the sequence of edges or the sequence of vertices provided during construction forms an actual * walk. It is the responsibility of the invoking class to provide correct input data. * *

* Note: Serialization of a GraphWalk implies the serialization of the entire underlying graph. *

* * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable * */ public class GraphWalk implements GraphPath, Serializable { private static final long serialVersionUID = 7663410644865380676L; protected Graph graph; protected List vertexList; protected List edgeList; protected V startVertex; protected V endVertex; protected double weight; /** * Creates a walk defined by a sequence of edges. A walk defined by its edges can be specified * for non-simple graphs. Edge repetition is permitted, the start and end point points ($v_0$ * and $v_k$) can be different. * * @param graph the graph * @param startVertex the starting vertex * @param endVertex the last vertex of the path * @param edgeList the list of edges of the path * @param weight the total weight of the path */ public GraphWalk(Graph graph, V startVertex, V endVertex, List edgeList, double weight) { this(graph, startVertex, endVertex, null, edgeList, weight); } /** * Creates a walk defined by a sequence of vertices. Note that the input graph must be simple, * otherwise the vertex sequence does not necessarily define a unique path. Furthermore, all * vertices must be pairwise adjacent. * * @param graph the graph * @param vertexList the list of vertices of the path * @param weight the total weight of the path */ public GraphWalk(Graph graph, List vertexList, double weight) { this( graph, (vertexList.isEmpty() ? null : vertexList.get(0)), (vertexList.isEmpty() ? null : vertexList.get(vertexList.size() - 1)), vertexList, null, weight); } /** * Creates a walk defined by both a sequence of edges and a sequence of vertices. Note that both * the sequence of edges and the sequence of vertices must describe the same path! This is not * verified during the construction of the walk. This constructor makes it possible to store * both a vertex and an edge view of the same walk, thereby saving computational overhead when * switching from one to the other. * * @param graph the graph * @param startVertex the starting vertex * @param endVertex the last vertex of the path * @param vertexList the list of vertices of the path * @param edgeList the list of edges of the path * @param weight the total weight of the path */ public GraphWalk( Graph graph, V startVertex, V endVertex, List vertexList, List edgeList, double weight) { // Some necessary but not sufficient conditions for valid paths if (vertexList == null && edgeList == null) throw new IllegalArgumentException("Vertex list and edge list cannot both be null!"); if (startVertex != null && vertexList != null && edgeList != null && edgeList.size() + 1 != vertexList.size()) throw new IllegalArgumentException( "VertexList and edgeList do not correspond to the same path (cardinality of vertexList +1 must equal the cardinality of the edgeList)"); if (startVertex == null ^ endVertex == null) throw new IllegalArgumentException( "Either the start and end vertices must both be null, or they must both be not null (one of them is null)"); this.graph = Objects.requireNonNull(graph); this.startVertex = startVertex; this.endVertex = endVertex; this.vertexList = vertexList; this.edgeList = edgeList; this.weight = weight; } @Override public Graph getGraph() { return graph; } @Override public V getStartVertex() { return startVertex; } @Override public V getEndVertex() { return endVertex; } @Override public List getEdgeList() { return (edgeList != null ? edgeList : GraphPath.super.getEdgeList()); } @Override public List getVertexList() { return (vertexList != null ? vertexList : GraphPath.super.getVertexList()); } @Override public double getWeight() { return weight; } /** * Updates the weight of this walk * * @param weight weight of the walk */ public void setWeight(double weight) { this.weight = weight; } @Override public int getLength() { if (edgeList != null) return edgeList.size(); else if (vertexList != null && !vertexList.isEmpty()) return vertexList.size() - 1; else return 0; } @Override public String toString() { if (vertexList != null) return vertexList.toString(); else return edgeList.toString(); } @Override public boolean equals(Object o) { if (o == null || !(o instanceof GraphWalk)) return false; else if (this == o) return true; @SuppressWarnings("unchecked") GraphWalk other = (GraphWalk) o; if (this.isEmpty() && other.isEmpty()) return true; if (this.isEmpty()) return false; if (!this.startVertex.equals(other.getStartVertex()) || !this.endVertex.equals(other.getEndVertex())) return false; // If this path is expressed as a vertex list, we may get away by comparing the other path's // vertex list // This only works if its vertexList identifies a unique path in the graph if (this.edgeList == null && !other.getGraph().getType().isAllowingMultipleEdges()) return this.vertexList.equals(other.getVertexList()); else // Unlucky, we need to compare the edge lists, return this.getEdgeList().equals(other.getEdgeList()); } @Override public int hashCode() { int hashCode = 1; if (isEmpty()) return hashCode; hashCode = 31 * hashCode + startVertex.hashCode(); hashCode = 31 * hashCode + endVertex.hashCode(); if (edgeList != null) return 31 * hashCode + edgeList.hashCode(); else return 31 * hashCode + vertexList.hashCode(); } /** * Reverses the direction of the walk. In case of directed/mixed graphs, the arc directions will * be reversed. An exception is thrown if reversing an arc $(u,v)$ is impossible because arc * $(v,u)$ is not present in the graph. The weight of the resulting walk equals the sum of edge * weights in the walk. * * @throws InvalidGraphWalkException if the path is invalid * @return a reversed GraphWalk */ public GraphWalk reverse() { return this.reverse(null); } /** * Reverses the direction of the walk. In case of directed/mixed graphs, the arc directions will * be reversed. An exception is thrown if reversing an arc $(u,v)$ is impossible because arc * $(v,u)$ is not present in the graph. * * @param walkWeightCalculator Function used to calculate the weight of the reversed GraphWalk * @throws InvalidGraphWalkException if the path is invalid * @return a reversed GraphWalk */ public GraphWalk reverse(Function, Double> walkWeightCalculator) { List revVertexList = null; List revEdgeList = null; double revWeight = 0; if (vertexList != null) { revVertexList = new ArrayList<>(this.vertexList); Collections.reverse(revVertexList); if (graph.getType().isUndirected()) revWeight = this.weight; // Check validity of the path. If the path is invalid, then calculating its weight may // result in an undefined exception. // If an edgeList is provided, then this check can be postponed to the construction of // the reversed edge list if (!graph.getType().isUndirected() && edgeList == null) { for (int i = 0; i < revVertexList.size() - 1; i++) { V u = revVertexList.get(i); V v = revVertexList.get(i + 1); E edge = graph.getEdge(u, v); if (edge == null) throw new InvalidGraphWalkException( "this walk cannot be reversed. The graph does not contain a reverse arc for arc " + graph.getEdge(v, u)); else revWeight += graph.getEdgeWeight(edge); } } } if (edgeList != null) { revEdgeList = new ArrayList<>(this.edgeList.size()); if (graph.getType().isUndirected()) { revEdgeList.addAll(this.edgeList); Collections.reverse(revEdgeList); revWeight = this.weight; } else { ListIterator listIterator = this.edgeList.listIterator(edgeList.size()); while (listIterator.hasPrevious()) { E e = listIterator.previous(); V u = graph.getEdgeSource(e); V v = graph.getEdgeTarget(e); E revEdge = graph.getEdge(v, u); if (revEdge == null) throw new InvalidGraphWalkException( "this walk cannot be reversed. The graph does not contain a reverse arc for arc " + e); revEdgeList.add(revEdge); revWeight += graph.getEdgeWeight(revEdge); } } } // Update weight of reversed walk GraphWalk gw = new GraphWalk<>( this.graph, this.endVertex, this.startVertex, revVertexList, revEdgeList, 0); if (walkWeightCalculator == null) gw.weight = revWeight; else gw.weight = walkWeightCalculator.apply(gw); return gw; } /** * Concatenates the specified GraphWalk to the end of this GraphWalk. This action can only be * performed if the end vertex of this GraphWalk is the same as the start vertex of the * extending GraphWalk * * @param extension GraphPath used for the concatenation. * @param walkWeightCalculator Function used to calculate the weight of the GraphWalk obtained * after the concatenation. * @return a GraphWalk that represents the concatenation of this object's walk followed by the * walk specified in the extension argument. */ public GraphWalk concat( GraphWalk extension, Function, Double> walkWeightCalculator) { if (this.isEmpty()) throw new IllegalArgumentException("An empty path cannot be extended"); if (!this.endVertex.equals(extension.getStartVertex())) throw new IllegalArgumentException( "This path can only be extended by another path if the end vertex of the orginal path and the start vertex of the extension are equal."); List concatVertexList = null; List concatEdgeList = null; if (vertexList != null) { concatVertexList = new ArrayList<>(this.vertexList); List vertexListExtension = extension.getVertexList(); concatVertexList.addAll(vertexListExtension.subList(1, vertexListExtension.size())); } if (edgeList != null) { concatEdgeList = new ArrayList<>(this.edgeList); concatEdgeList.addAll(extension.getEdgeList()); } GraphWalk gw = new GraphWalk<>( this.graph, startVertex, extension.getEndVertex(), concatVertexList, concatEdgeList, 0); gw.setWeight(walkWeightCalculator.apply(gw)); return gw; } /** * Returns true if the path is an empty path, that is, a path with startVertex=endVertex=null * and with an empty vertex and edge list. * * @return Returns true if the path is an empty path. */ public boolean isEmpty() { return startVertex == null; } /** * Convenience method which verifies whether the given path is feasible wrt the input graph and * forms an actual path. * * @throws InvalidGraphWalkException if the path is invalid */ public void verify() { if (isEmpty()) // Empty path return; if (vertexList != null && !vertexList.isEmpty()) { if (!startVertex.equals(vertexList.get(0))) throw new InvalidGraphWalkException( "The start vertex must be the first vertex in the vertex list"); if (!endVertex.equals(vertexList.get(vertexList.size() - 1))) throw new InvalidGraphWalkException( "The end vertex must be the last vertex in the vertex list"); // All vertices and edges in the path must be contained in the graph if (!graph.vertexSet().containsAll(vertexList)) throw new InvalidGraphWalkException( "Not all vertices in the path are contained in the graph"); if (edgeList == null) { // Verify sequence Iterator it = vertexList.iterator(); V u = it.next(); while (it.hasNext()) { V v = it.next(); if (graph.getEdge(u, v) == null) throw new InvalidGraphWalkException( "The vertexList does not constitute to a feasible path. Edge (" + u + "," + v + " does not exist in the graph."); u = v; } } } if (edgeList != null && !edgeList.isEmpty()) { if (!Graphs.testIncidence(graph, edgeList.get(0), startVertex)) throw new InvalidGraphWalkException( "The first edge in the edge list must leave the start vertex"); if (!graph.edgeSet().containsAll(edgeList)) throw new InvalidGraphWalkException( "Not all edges in the path are contained in the graph"); if (vertexList == null) { V u = startVertex; for (E edge : edgeList) { if (!Graphs.testIncidence(graph, edge, u)) throw new InvalidGraphWalkException( "The edgeList does not constitute to a feasible path. Conflicting edge: " + edge); u = Graphs.getOppositeVertex(graph, edge, u); } if (!u.equals(endVertex)) throw new InvalidGraphWalkException( "The path defined by the edgeList does not end in the endVertex."); } } if (vertexList != null && edgeList != null) { // Verify that the path is an actual path in the graph if (edgeList.size() + 1 != vertexList.size()) throw new InvalidGraphWalkException( "VertexList and edgeList do not correspond to the same path (cardinality of vertexList +1 must equal the cardinality of the edgeList)"); for (int i = 0; i < vertexList.size() - 1; i++) { V u = vertexList.get(i); V v = vertexList.get(i + 1); E edge = getEdgeList().get(i); if (graph.getType().isDirected()) { // Directed graph if (!graph.getEdgeSource(edge).equals(u) || !graph.getEdgeTarget(edge).equals(v)) throw new InvalidGraphWalkException( "VertexList and edgeList do not form a feasible path"); } else { // Undirected or mixed if (!Graphs.testIncidence(graph, edge, u) || !Graphs.getOppositeVertex(graph, edge, u).equals(v)) throw new InvalidGraphWalkException( "VertexList and edgeList do not form a feasible path"); } } } } /** * Convenience method which creates an empty walk. * * @param graph input graph * @param vertex type * @param edge type * @return an empty walk */ public static GraphWalk emptyWalk(Graph graph) { return new GraphWalk<>( graph, null, null, Collections.emptyList(), Collections.emptyList(), 0.0); } /** * Convenience method which creates a walk consisting of a single vertex with weight 0.0. * * @param graph input graph * @param v single vertex * @param vertex type * @param edge type * @return an empty walk */ public static GraphWalk singletonWalk(Graph graph, V v) { return singletonWalk(graph, v, 0d); } /** * Convenience method which creates a walk consisting of a single vertex. * * @param graph input graph * @param v single vertex * @param weight weight of the path * @param vertex type * @param edge type * @return an empty walk */ public static GraphWalk singletonWalk(Graph graph, V v, double weight) { return new GraphWalk<>( graph, v, v, Collections.singletonList(v), Collections.emptyList(), weight); } } /** * Exception thrown in the event that the path is invalid. */ class InvalidGraphWalkException extends RuntimeException { private static final long serialVersionUID = 3811666107707436479L; public InvalidGraphWalkException(String message) { super(message); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy