com.googlecode.blaisemath.graph.GraphUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of blaise-graphtheory Show documentation
Show all versions of blaise-graphtheory Show documentation
Link graph definitions, algorithms, and visualization.
The newest version!
/**
* GraphUtils.java Created on Oct 14, 2009
*/
package com.googlecode.blaisemath.graph;
/*
* #%L
* BlaiseGraphTheory
* --
* Copyright (C) 2009 - 2016 Elisha Peterson
* --
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.common.collect.Table.Cell;
import com.googlecode.blaisemath.linear.Matrices;
import com.googlecode.blaisemath.util.Edge;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
/**
* Contains several utility methods for creating and analyzing graphs.
*
* @author Elisha Peterson
*/
public class GraphUtils {
/** Used to sort graphs in descending order by size */
public static final Comparator GRAPH_SIZE_DESCENDING = new Comparator() {
@Override
public int compare(Graph o1, Graph o2) {
return -(o1.nodeCount() == o2.nodeCount() && o1.edgeCount() == o2.edgeCount()
? o1.nodes().toString().compareTo(o2.nodes().toString())
: o1.nodeCount() == o2.nodeCount()
? o1.edgeCount() - o2.edgeCount()
: o1.nodeCount() - o2.nodeCount());
}
};
// utility class
private GraphUtils() {
}
//
//
// GENERIC PRINT METHODS
//
/**
* Returns string representation of specified graph
* @param graph the graph to print
* @return string representation of graph
*/
public static String printGraph(Graph> graph) {
return printGraph(graph, true, true);
}
/**
* Returns string representation of specified graph
* @param graph node type
* @param graph the graph to print
* @param printNodes whether to print nodes or not
* @param printEdges whether to print edges or not
* @return string representation of graph
*/
public static String printGraph(Graph graph, boolean printNodes, boolean printEdges) {
if (!printEdges && !printNodes) {
return "GRAPH";
} else if (printNodes && !printEdges) {
Set nodes = graph.nodes();
if (nodes.size() > 0 && nodes.iterator().next() instanceof Comparable) {
nodes = new TreeSet(nodes);
}
return "NODES: " + nodes;
}
Set nodes = graph.nodes();
boolean sortable = nodes.size() > 0 && nodes.iterator().next() instanceof Comparable;
if (sortable) {
nodes = new TreeSet(nodes);
}
StringBuilder result = new StringBuilder();
if (printNodes) {
result.append("NODES: ").append(nodes).append(" ");
}
result.append("EDGES:");
for (V v : nodes) {
result.append(" ").append(v).append(": ")
.append(sortable ? new TreeSet(graph.outNeighbors(v)) : graph.outNeighbors(v));
}
return result.toString();
}
//
//
//
// DUPLICATION METHODS
//
/**
* Creates a copy of a set of edges.
* @param graph node type
* @param edges source edges
* @return copy of edges
*/
public static Iterable> copyEdges(Iterable extends Edge> edges) {
List> res = Lists.newArrayList();
for (Edge e : edges) {
res.add(new Edge(e));
}
return res;
}
/**
* Creates a copy of the specified graph by iterating through all possible
* adjacencies. Also optimizes the underlying representation by choosing
* either a sparse graph or a matrix graph representation. If the input
* graph has extra properties, such as node labels, they are not included in
* the copy.
* @param graph node type
* @param graph a graph
* @return an instance of the graph with the same vertices and edges, but a new copy of it
*/
public static Graph copyAsSparseGraph(Graph graph) {
return SparseGraph.createFromEdges(graph.isDirected(), graph.nodes(), copyEdges(graph.edges()));
}
/**
* Creates an undirected copy of the specified graph.
* @param graph node type
* @param graph a graph
* @return undirected copy with the same collection of edges
*/
public static Graph copyAsUndirectedSparseGraph(Graph graph) {
return SparseGraph.createFromEdges(false, graph.nodes(), copyEdges(graph.edges()));
}
//
//
//
// SUBGRAPHS
//
/**
* Create a subgraph of a parent graph.
* @param graph node type
* @param parent parent graph
* @param nodes nodes of parent to keep
* @return new graph
*/
public static Graph copySubgraph(Graph parent, final Set nodes) {
Iterable> filteredEdges = Iterables.filter(parent.edges(),
new Predicate>(){
@Override
public boolean apply(Edge input) {
return nodes.contains(input.getNode1())
&& nodes.contains(input.getNode2());
}
});
return SparseGraph.createFromEdges(parent.isDirected(), nodes, filteredEdges);
}
/**
* Extract the core graph from a parent graph, consisting of only nodes
* with degree at least 2.
* @param graph node type
* @param parent parent graph
* @return graph with isolates and leaves pruned
*/
public static Graph core(Graph parent) {
Set cNodes = Sets.newLinkedHashSet();
if (parent instanceof OptimizedGraph) {
OptimizedGraph og = (OptimizedGraph) parent;
cNodes.addAll(og.getCoreNodes());
cNodes.addAll(og.getConnectorNodes());
} else {
for (V v : parent.nodes()) {
if (parent.degree(v) >= 2) {
cNodes.add(v);
}
}
}
return copySubgraph(parent, cNodes);
}
//
//
//
// ADJACENCY MATRIX METHODS
//
/**
* Computes adjacency matrix of a graph
* @param graph node type
* @param graph the input graph
* @param order if empty, will be filled with order of nodes; if non-empty, will be used to order nodes in graph
* @return matrix of integers describing adjacencies... contains 0's and
* 1's... it is symmetric when the graph is copyUndirected, otherwise it may
* not be symmetric
*/
public static boolean[][] adjacencyMatrix(Graph graph, List order) {
if (order == null) {
throw new IllegalArgumentException();
}
if (order.isEmpty()) {
order.addAll(graph.nodes());
}
int n = order.size();
boolean[][] result = new boolean[n][n];
for (int i1 = 0; i1 < n; i1++) {
for (int i2 = 0; i2 < n; i2++) {
result[i1][i2] = graph.isDirected() ? graph.outNeighbors(order.get(i1)).contains(order.get(i2))
: graph.adjacent(order.get(i1), order.get(i2));
}
}
return result;
}
/**
* Computes the adjacency matrix and several of its powers.
* @param graph node type
* @param graph the input graph
* @param order if empty, will be filled with order of nodes; if non-empty, will be used to order nodes in graph
* @param maxPower maximum power of the adjacency matrix to include in
* result
* @return matrix of integers describing adjacencies... contains 0's and
* 1's... it is symmetric when the graph is copyUndirected, otherwise it may
* not be symmetric
*/
public static int[][][] adjacencyMatrixPowers(Graph graph, List order, int maxPower) {
boolean[][] adj0 = adjacencyMatrix(graph, order);
int[][] adj1 = new int[adj0.length][adj0.length];
for (int i = 0; i < adj1.length; i++) {
for (int j = 0; j < adj1.length; j++) {
adj1[i][j] = adj0[i][j] ? 1 : 0;
}
}
int[][][] result = new int[maxPower][adj1.length][adj1[0].length];
result[0] = adj1;
int cur = 2;
while (cur <= maxPower) {
result[cur - 1] = Matrices.matrixProduct(result[cur - 2], adj1);
cur++;
}
return result;
}
//
//
//
// DEGREE METHODS
//
/**
* Return function calculating degree of elements in the given graph.
* @param graph node type
* @param graph graph
* @return function providing degree of vertices
*/
public static Function degreeFunction(final Graph graph) {
return new Function() {
@Override
public Integer apply(V input) {
return graph.degree(input);
}
};
}
/**
* Computes and returns degree distribution.
* @param graph node type
* @param graph the graph
* @return map associating degree #s with counts, sorted by degree
*/
public static Multiset degreeDistribution(Graph graph) {
return HashMultiset.create(Iterables.transform(graph.nodes(), degreeFunction(graph)));
}
//
//
//
// GEODESIC & SPANNING TREE METHODS
//
/**
* Computes and creates a tree describing geodesic distances from a
* specified vertex. Choice of geodesic when multiple are possible is
* unspecified.
* @param graph node type
* @param graph the starting graph
* @param vertex the starting vertex
* @return map with vertex distance lengths
*/
public static Map geodesicTree(Graph graph, V vertex) {
return geodesicTree(graph, vertex, Integer.MAX_VALUE);
}
/**
* Computes and creates a tree describing geodesic distances from a
* specified vertex, up through a distance specified by the max parameter.
* Choice of geodesic when multiple are possible is unspecified. The graph
* only contains the vertices that are in the same component as the starting
* vertex (forward component if directed).
* @param graph node type
* @param graph the starting graph
* @param vertex the starting vertex
* @param max the maximum distance to proceed from the starting vertex
* @return graph with objects associated to each vertex that describe the
* distance from the main vertex.
*/
public static Map geodesicTree(Graph graph, V vertex, int max) {
// vertices left to add
Set remaining = Sets.newHashSet(graph.nodes());
// vertices added already, by distance
List> added = Lists.newArrayList();
// stores size of remaining vertices
int sRemaining = -1;
remaining.remove(vertex);
added.add(new HashSet(Arrays.asList(vertex)));
while (sRemaining != remaining.size() && added.size() < (max == Integer.MAX_VALUE ? max : max + 1)) {
sRemaining = remaining.size();
added.add(new HashSet());
for (V v1 : added.get(added.size() - 2)) {
Set toRemove = Sets.newHashSet();
for (V v2 : remaining) {
if (graph.adjacent(v1, v2)) {
toRemove.add(v2);
added.get(added.size() - 1).add(v2);
V[] arr = (V[]) Array.newInstance(v1.getClass(), 2);
arr[0] = v1;
arr[1] = v2;
}
}
remaining.removeAll(toRemove);
}
}
Map result = new HashMap();
for (int i = 0; i < added.size(); i++) {
for (V v : added.get(i)) {
result.put(v, i);
}
}
return result;
}
/**
* Finds geodesic distance between two vertices in a graph
* @param graph node type
* @param graph the graph
* @param start first vertex
* @param end second vertex
* @return the geodesic distance between the vertices, or 0 if they are the
* same vertex, or -1 if they are not connected
*/
public static int geodesicDistance(Graph graph, V start, V end) {
if (start.equals(end)) {
return 0;
}
if (!(graph.contains(start) && graph.contains(end))) {
return -1;
}
// vertices left to add
ArrayList remaining = Lists.newArrayList(graph.nodes());
// vertices added already, by distance
ArrayList> added = Lists.newArrayList();
// stores size of remaining vertices
int sRemaining;
remaining.remove(start);
added.add(Sets.newHashSet(start));
do {
sRemaining = remaining.size();
added.add(new HashSet());
for (V v1 : added.get(added.size() - 2)) {
HashSet toRemove = new HashSet();
for (V v2 : remaining) {
if (graph.adjacent(v1, v2)) {
if (v2.equals(end)) {
return added.size() - 1;
}
toRemove.add(v2);
added.get(added.size() - 1).add(v2);
}
}
remaining.removeAll(toRemove);
}
} while (sRemaining != remaining.size());
return -1;
}
//
//
//
// NEIGHBORHOOD & COMPONENT METHODS
//
/**
* Generates ordered set of nodes from an adjacency map
* @param graph node type
* @param adj an adjacency map
* @return list of nodes
*/
public static Set nodes(Multimap adj) {
Set result = Sets.newLinkedHashSet();
result.addAll(adj.keySet());
result.addAll(adj.values());
return result;
}
/**
* Computes neighborhood about provided vertex up to a given radius, as a
* set of vertices. The result always includes the vertex itself.
* @param graph node type
* @param graph the graph
* @param vertex the starting vertex
* @param radius the maximum distance to consider
* @return a list containing the vertices within the neighborhood
*/
public static Set neighborhood(Graph graph, V vertex, int radius) {
return geodesicTree(graph, vertex, radius).keySet();
}
/**
* Generates connected components from an adjacency map.
* @param graph node type
* @param adj an adjacency map
* @return set of components, as a set of sets
*/
public static Collection> components(Multimap adj) {
Map> setMap = Maps.newLinkedHashMap();
for (Entry en : adj.entries()) {
V v1 = en.getKey();
V v2 = en.getValue();
boolean v1InSet = setMap.containsKey(v1);
boolean v2InSet = setMap.containsKey(v2);
if (v1InSet && v2InSet) {
// check if components need to be merged
if (setMap.get(v1) != setMap.get(v2)) {
Set nue = Sets.newHashSet(Iterables.concat(setMap.get(v1), setMap.get(v2)));
for (V v : nue) {
setMap.put(v, nue);
}
}
} else if (v1InSet) {
// v2 hasn't been seen before
Set set = setMap.get(v1);
set.add(v2);
setMap.put(v2, set);
} else if (v2InSet) {
// v1 hasn't been seen before
Set set = setMap.get(v2);
set.add(v1);
setMap.put(v1, set);
} else {
// create new set with v1 and v2
Set set = Sets.newHashSet(v1, v2);
setMap.put(v1, set);
setMap.put(v2, set);
}
}
return Sets.newLinkedHashSet(setMap.values());
}
/**
* Generates connected components from an adjacency map.
* @param graph node type
* @param adj an adjacency map
* @return set of components, as a set of sets
*/
public static Collection> components(Table adj) {
Multimap multimap = LinkedHashMultimap.create();
for (Cell cell : adj.cellSet()) {
multimap.put(cell.getRowKey(), cell.getColumnKey());
}
return components(multimap);
}
/**
* Generates connected components from a graph.
* @param graph node type
* @param graph the graph
* @return set of connected components
*/
public static Collection> components(Graph graph) {
if (graph instanceof SparseGraph) {
return ((SparseGraph) graph).getComponentInfo().getComponents();
} else {
return components(adjacencies(graph, graph.nodes()));
}
}
/**
* Generates connected components from a subset of vertices in a graph.
* @param graph node type
* @param graph the graph
* @param nodes subset of nodes
* @return set of connected components
*/
public static Collection> components(Graph graph, Set nodes) {
return components(adjacencies(graph, nodes));
}
/**
* Generates connected components as a list of subgraphs.
* @param graph node type
* @param graph the graph of interest
* @return set of connected component subgraphs
*/
public static Set> componentGraphs(Graph graph) {
int id = GAInstrument.start("componentGraphs", "" + graph.nodeCount());
Set> result = graph instanceof SparseGraph
? ((SparseGraph) graph).getComponentInfo().getComponentGraphs()
: new GraphComponents(graph, components(graph)).getComponentGraphs();
GAInstrument.end(id);
return result;
}
/**
* Generates adjacency map from a subgraph.
* @param graph node type
* @param graph the graph
* @param nodes subset of nodes
* @return adjacency map restricted to the given subset
*/
public static Multimap adjacencies(Graph graph, Set nodes) {
Multimap res = LinkedHashMultimap.create();
for (V v : nodes) {
res.putAll(v, Sets.intersection(graph.neighbors(v), nodes));
}
return res;
}
/**
* Performs breadth-first search algorithm to enumerate the nodes in a
* graph, starting from the specified start node.
* @param graph node type
* @param graph the graph under consideration
* @param start the starting node.
* @param numShortest a map that will be filled with info on the # of
* shortest paths
* @param lengths a map that will be filled with info on the lengths of
* shortest paths
* @param stack a stack that will be filled with elements in non-increasing
* order of distance
* @param pred a map that will be filled with adjacency information for the
* shortest paths
*/
public static void breadthFirstSearch(Graph graph, V start,
Map numShortest, Map lengths,
Stack stack, Map> pred) {
Set nodes = graph.nodes();
for (V v : nodes) {
numShortest.put(v, 0);
}
numShortest.put(start, 1);
for (V v : nodes) {
lengths.put(v, -1);
}
lengths.put(start, 0);
for (V v : nodes) {
pred.put(v, new HashSet());
}
// breadth-first search algorithm
LinkedList queue = new LinkedList(); // tracks elements for search algorithm
queue.add(start);
while (!queue.isEmpty()) {
V v = queue.remove();
stack.addElement(v);
for (V w : graph.neighbors(v)) {
// if w is found for the first time in the tree, add it to the queue, and adjust the length
if (lengths.get(w) == -1) {
queue.add(w);
lengths.put(w, lengths.get(v) + 1);
}
// adjust the number of shortest paths to w if shortest path goes through v
if (lengths.get(w) == lengths.get(v) + 1) {
numShortest.put(w, numShortest.get(w) + numShortest.get(v));
pred.get(w).add(v);
}
}
}
}
//
//
//
// CONTRACTED ELEMENTS
//
/**
* Creates a contracted graph from a parent graph, where all of a specified
* subset of nodes are contracted to a single node
* @param graph node type
* @param graph parent graph
* @param contract nodes to contract
* @param replace node to replace all contracted nodes
* @return graph where the specified nodes have been contracted
*/
public static Graph contractedGraph(Graph graph, Collection contract, V replace) {
List> edges = Lists.newArrayList();
for (Edge e : graph.edges()) {
Edge edge = new Edge(
contract.contains(e.getNode1()) ? replace : e.getNode1(),
contract.contains(e.getNode2()) ? replace : e.getNode2());
edges.add(edge);
}
return SparseGraph.createFromEdges(graph.isDirected(), contractedNodeSet(graph.nodes(), contract, replace), edges);
}
/**
* Contracts list of nodes, replacing all the "contract" nodes with
* "replace".
* @param graph node type
* @param nodes collection of nodes
* @param contract nodes to contract
* @param replace node to replace all contracted nodes
* @return node set
*/
public static Set contractedNodeSet(Collection nodes, Collection contract, V replace) {
Set result = Sets.newHashSet(nodes);
result.removeAll(contract);
result.add(replace);
return result;
}
/**
* Contracts list of components, combining all components with vertices in subset.
* @param graph node type
* @param components list of components to contract
* @param subset subset to contract
* @param vertex what to replace contracted subset with
* @return contracted components
*/
public static Set> contractedComponents(Collection> components, Collection subset, V vertex) {
HashSet> result =Sets.newHashSet();
Set contracted = Sets.newHashSet();
contracted.add(vertex);
result.add(contracted);
for (Set c : components) {
if (Collections.disjoint(c, subset)) {
result.add(Sets.newHashSet(c));
} else {
contracted.addAll(c);
}
}
return result;
}
//
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy