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

org.jgrapht.GraphMetrics Maven / Gradle / Ivy

/*
 * (C) Copyright 2017-2021, 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;

import org.jgrapht.alg.shortestpath.*;
import org.jgrapht.alg.util.*;
import org.jgrapht.util.*;

import java.util.*;
import java.util.stream.*;

/**
 * Collection of methods which provide numerical graph information.
 *
 * @author Joris Kinable
 * @author Alexandru Valeanu
 */
public abstract class GraphMetrics
{

    /**
     * Compute the diameter of the
     * graph. The diameter of a graph is defined as $\max_{v\in V}\epsilon(v)$, where $\epsilon(v)$
     * is the eccentricity of vertex $v$. In other words, this method computes the 'longest shortest
     * path'. Two special cases exist. If the graph has no vertices, the diameter is 0. If the graph
     * is disconnected, the diameter is {@link Double#POSITIVE_INFINITY}.
     * 

* For more fine-grained control over this method, or if you need additional distance metrics * such as the graph radius, consider using {@link org.jgrapht.alg.shortestpath.GraphMeasurer} * instead. * * @param graph input graph * @param graph vertex type * @param graph edge type * @return the diameter of the graph. */ public static double getDiameter(Graph graph) { return new GraphMeasurer<>(graph).getDiameter(); } /** * Compute the radius of the graph. * The radius of a graph is defined as $\min_{v\in V}\epsilon(v)$, where $\epsilon(v)$ is the * eccentricity of vertex $v$. Two special cases exist. If the graph has no vertices, the radius * is 0. If the graph is disconnected, the diameter is {@link Double#POSITIVE_INFINITY}. *

* For more fine-grained control over this method, or if you need additional distance metrics * such as the graph diameter, consider using {@link org.jgrapht.alg.shortestpath.GraphMeasurer} * instead. * * @param graph input graph * @param graph vertex type * @param graph edge type * @return the diameter of the graph. */ public static double getRadius(Graph graph) { return new GraphMeasurer<>(graph).getRadius(); } /** * Compute the girth of the graph. The * girth of a graph is the length (number of edges) of the smallest cycle in the graph. Acyclic * graphs are considered to have infinite girth. For directed graphs, the length of the shortest * directed cycle is returned (see Bang-Jensen, J., Gutin, G., Digraphs: Theory, Algorithms and * Applications, Springer Monographs in Mathematics, ch 1, ch 8.4.). Simple undirected graphs * have a girth of at least 3 (triangle cycle). Directed graphs and Multigraphs have a girth of * at least 2 (parallel edges/arcs), and in Pseudo graphs have a girth of at least 1 * (self-loop). *

* This implementation is loosely based on these notes. * In essence, this method invokes a Breadth-First search from every vertex in the graph. A * single Breadth-First search takes $O(n+m)$ time, where $n$ is the number of vertices in the * graph, and $m$ the number of edges. Consequently, the runtime complexity of this method is * $O(n(n+m))=O(mn)$. *

* An algorithm with the same worst case runtime complexity, but a potentially better average * runtime complexity of $O(n^2)$ is described in: Itai, A. Rodeh, M. Finding a minimum circuit * in a graph. SIAM J. Comput. Vol 7, No 4, 1987. * * @param graph input graph * @param graph vertex type * @param graph edge type * @return girth of the graph, or {@link Integer#MAX_VALUE} if the graph is acyclic. */ public static int getGirth(Graph graph) { final int nil = -1; final boolean isAllowingMultipleEdges = graph.getType().isAllowingMultipleEdges(); // Ordered sequence of vertices List vertices = new ArrayList<>(graph.vertexSet()); // Index map of vertices in ordered sequence Map indexMap = new HashMap<>(); for (int i = 0; i < vertices.size(); i++) indexMap.put(vertices.get(i), i); // Objective int girth = Integer.MAX_VALUE; // Array storing the depth of each vertex in the search tree int[] depth = new int[vertices.size()]; // Queue for BFS Queue queue = new ArrayDeque<>(); // Check whether the graph has self-loops if (graph.getType().isAllowingSelfLoops()) for (V v : vertices) if (graph.containsEdge(v, v)) return 1; NeighborCache neighborIndex = new NeighborCache<>(graph); if (graph.getType().isUndirected()) { // Array which keeps track of the search tree structure to prevent revisiting parent // nodes int[] parent = new int[vertices.size()]; // Start a BFS search tree from each vertex. The search stops when a triangle (smallest // possible cycle) is found. // The last two vertices can be ignored. for (int i = 0; i < vertices.size() - 2 && girth > 3; i++) { // Reset data structures Arrays.fill(depth, nil); Arrays.fill(parent, nil); queue.clear(); depth[i] = 0; queue.add(vertices.get(i)); int depthU; do { V u = queue.poll(); int indexU = indexMap.get(u); depthU = depth[indexU]; // Visit all neighbors of vertex u for (V v : neighborIndex.neighborsOf(u)) { int indexV = indexMap.get(v); if (parent[indexU] == indexV) { // Skip the parent of vertex u, unless there // are multiple edges between u and v if (!isAllowingMultipleEdges || graph.getAllEdges(u, v).size() == 1) continue; } int depthV = depth[indexV]; if (depthV == nil) { // New neighbor discovered queue.add(v); depth[indexV] = depthU + 1; parent[indexV] = indexU; } else { // Rediscover neighbor: found cycle. girth = Math.min(girth, depthU + depthV + 1); } } } while (!queue.isEmpty() && 2 * (depthU + 1) - 1 < girth); } } else { // Directed case for (int i = 0; i < vertices.size() - 1 && girth > 2; i++) { // Reset data structures Arrays.fill(depth, nil); queue.clear(); depth[i] = 0; queue.add(vertices.get(i)); int depthU; do { V u = queue.poll(); int indexU = indexMap.get(u); depthU = depth[indexU]; // Visit all neighbors of vertex u for (V v : neighborIndex.successorsOf(u)) { int indexV = indexMap.get(v); int depthV = depth[indexV]; if (depthV == nil) { // New neighbor discovered queue.add(v); depth[indexV] = depthU + 1; } else if (depthV == 0) { // Rediscover root: found cycle. girth = Math.min(girth, depthU + depthV + 1); } } } while (!queue.isEmpty() && depthU + 1 < girth); } } assert graph.getType().isUndirected() && graph.getType().isSimple() && girth >= 3 || graph.getType().isAllowingSelfLoops() && girth >= 1 || girth >= 2 && (graph.getType().isDirected() || graph.getType().isAllowingMultipleEdges()); return girth; } /** * An $O(|V|^3)$ (assuming vertexSubset provides constant time indexing) naive implementation * for counting non-trivial triangles in an undirected graph induced by the subset of vertices. * * @param graph the input graph * @param vertexSubset the vertex subset * @param the graph vertex type * @param the graph edge type * @return the number of triangles in the graph induced by vertexSubset */ static long naiveCountTriangles(Graph graph, List vertexSubset) { long total = 0; if (graph.getType().isAllowingMultipleEdges()) { for (int i = 0; i < vertexSubset.size(); i++) { for (int j = i + 1; j < vertexSubset.size(); j++) { for (int k = j + 1; k < vertexSubset.size(); k++) { V u = vertexSubset.get(i); V v = vertexSubset.get(j); V w = vertexSubset.get(k); int uvEdgeCount = graph.getAllEdges(u, v).size(); if (uvEdgeCount == 0) { continue; } int vwEdgeCount = graph.getAllEdges(v, w).size(); if (vwEdgeCount == 0) { continue; } int wuEdgeCount = graph.getAllEdges(w, u).size(); if (wuEdgeCount == 0) { continue; } total += uvEdgeCount * vwEdgeCount * wuEdgeCount; } } } } else { for (int i = 0; i < vertexSubset.size(); i++) { for (int j = i + 1; j < vertexSubset.size(); j++) { for (int k = j + 1; k < vertexSubset.size(); k++) { V u = vertexSubset.get(i); V v = vertexSubset.get(j); V w = vertexSubset.get(k); if (graph.containsEdge(u, v) && graph.containsEdge(v, w) && graph.containsEdge(w, u)) { total++; } } } } } return total; } /** * An $O(|E|^{3/2})$ algorithm for counting the number of non-trivial triangles in an undirected * graph. A non-trivial triangle is formed by three distinct vertices all connected to each * other. * *

* For more details of this algorithm see Ullman, Jeffrey: "Mining of Massive Datasets", * Cambridge University Press, Chapter 10 * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return the number of triangles in the graph * @throws NullPointerException if {@code graph} is {@code null} * @throws IllegalArgumentException if {@code graph} is not undirected */ public static long getNumberOfTriangles(Graph graph) { GraphTests.requireUndirected(graph); final int sqrtV = (int) Math.sqrt(graph.vertexSet().size()); List vertexList = new ArrayList<>(graph.vertexSet()); /* * The book suggest the following comparator: "Compare vertices based on their degree. If * equal compare them of their actual value, since they are all integers". */ // Fix vertex order for unique comparison of vertices Map vertexOrder = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); int k = 0; for (V v : graph.vertexSet()) { vertexOrder.put(v, k++); } Comparator comparator = Comparator .comparingInt(graph::degreeOf).thenComparingInt(System::identityHashCode) .thenComparingInt(vertexOrder::get); vertexList.sort(comparator); // vertex v is a heavy-hitter iff degree(v) >= sqrtV List heavyHitterVertices = vertexList .stream().filter(x -> graph.degreeOf(x) >= sqrtV) .collect(Collectors.toCollection(ArrayList::new)); // count the number of triangles formed from only heavy-hitter vertices long numberTriangles = naiveCountTriangles(graph, heavyHitterVertices); for (E edge : graph.edgeSet()) { V v1 = graph.getEdgeSource(edge); V v2 = graph.getEdgeTarget(edge); if (v1 == v2) { continue; } if (graph.degreeOf(v1) < sqrtV || graph.degreeOf(v2) < sqrtV) { // ensure that v1 <= v2 (swap them otherwise) if (comparator.compare(v1, v2) > 0) { V tmp = v1; v1 = v2; v2 = tmp; } for (E e : graph.edgesOf(v1)) { V u = Graphs.getOppositeVertex(graph, e, v1); // check if the triangle is non-trivial: u, v1, v2 are distinct vertices if (u == v1 || u == v2) { continue; } /* * Check if v2 <= u and if (u, v2) is a valid edge. If both of them are true, * then we have a new triangle (v1, v2, u) and all three vertices in the * triangle are ordered (v1 <= v2 <= u) so we count it only once. */ if (comparator.compare(v2, u) <= 0 && graph.containsEdge(u, v2)) { numberTriangles++; } } } } return numberTriangles; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy