com.salesforce.jgrapht.alg.spanning.GreedyMultiplicativeSpanner Maven / Gradle / Ivy
Show all versions of AptSpringProcessor Show documentation
/*
* (C) Copyright 2016-2017, by Dimitrios Michail and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* This program and the accompanying materials are dual-licensed under
* either
*
* (a) the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation, or (at your option) any
* later version.
*
* or (per the licensee's choosing)
*
* (b) the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation.
*/
package com.salesforce.jgrapht.alg.spanning;
import java.util.*;
import com.salesforce.jgrapht.*;
import com.salesforce.jgrapht.alg.interfaces.*;
import com.salesforce.jgrapht.graph.*;
import com.salesforce.jgrapht.util.*;
/**
* Greedy algorithm for (2k-1)-multiplicative spanner construction (for any integer
* {@literal k >= 1}).
*
*
* The spanner is guaranteed to contain O(n^{1+1/k}) edges and the shortest path distance between
* any two vertices in the spanner is at most 2k-1 times the corresponding shortest path distance in
* the original graph. Here n denotes the number of vertices of the graph.
*
*
* The algorithm is described in: Althoefer, Das, Dobkin, Joseph, Soares.
* On Sparse Spanners of Weighted Graphs. Discrete
* Computational Geometry 9(1):81-100, 1993.
*
*
* If the graph is unweighted the algorithm runs in O(m n^{1+1/k}) time. Setting k to infinity will
* result in a slow version of Kruskal's algorithm where cycle detection is performed by a BFS
* computation. In such a case use the implementation of Kruskal with union-find. Here n and m are
* the number of vertices and edges of the graph respectively.
*
*
* If the graph is weighted the algorithm runs in O(m (n^{1+1/k} + nlogn)) time by using Dijkstra's
* algorithm. Edge weights must be non-negative.
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Dimitrios Michail
* @since July 15, 2016
*/
public class GreedyMultiplicativeSpanner
implements SpannerAlgorithm
{
private final UndirectedGraph graph;
private final int k;
private static final int MAX_K = 1 << 29;
/**
* Constructs instance to compute a (2k-1)-spanner of an undirected graph.
*
* @param graph an undirected graph
* @param k positive integer.
*/
public GreedyMultiplicativeSpanner(UndirectedGraph graph, int k)
{
this.graph = Objects.requireNonNull(graph, "Graph cannot be null");
if (k <= 0) {
throw new IllegalArgumentException(
"k should be positive in (2k-1)-spanner construction");
}
this.k = Math.min(k, MAX_K);
}
@Override
public Spanner getSpanner()
{
if (graph instanceof WeightedGraph) {
return new WeightedSpannerAlgorithm().run();
} else {
return new UnweightedSpannerAlgorithm().run();
}
}
// base algorithm implementation
private abstract class SpannerAlgorithmBase
{
public abstract boolean isSpannerReachable(V s, V t, double distance);
public abstract void addSpannerEdge(V s, V t, double weight);
public Spanner run()
{
// sort edges
ArrayList allEdges = new ArrayList<>(graph.edgeSet());
Collections.sort(
allEdges, (e1, e2) -> Double
.valueOf(graph.getEdgeWeight(e1)).compareTo(graph.getEdgeWeight(e2)));
// check precondition
double minWeight = graph.getEdgeWeight(allEdges.get(0));
if (minWeight < 0.0) {
throw new IllegalArgumentException("Illegal edge weight: negative");
}
// run main loop
Set edgeList = new LinkedHashSet<>();
double edgeListWeight = 0d;
for (E e : allEdges) {
V s = graph.getEdgeSource(e);
V t = graph.getEdgeTarget(e);
if (!s.equals(t)) { // self-loop?
double eWeight = graph.getEdgeWeight(e);
if (!isSpannerReachable(s, t, (2 * k - 1) * eWeight)) {
edgeList.add(e);
edgeListWeight += eWeight;
addSpannerEdge(s, t, eWeight);
}
}
}
return new SpannerImpl<>(edgeList, edgeListWeight);
}
}
private class UnweightedSpannerAlgorithm
extends SpannerAlgorithmBase
{
protected UndirectedGraph spanner;
protected Map vertexDistance;
protected Deque queue;
protected Deque touchedVertices;
public UnweightedSpannerAlgorithm()
{
spanner = new SimpleGraph(graph.getEdgeFactory());
touchedVertices = new ArrayDeque(graph.vertexSet().size());
for (V v : graph.vertexSet()) {
spanner.addVertex(v);
touchedVertices.push(v);
}
vertexDistance = new HashMap(graph.vertexSet().size());
queue = new ArrayDeque<>();
}
/**
* Check if two vertices are reachable by a BFS in the spanner graph using only a certain
* number of hops.
*
* We execute this procedure repeatedly, therefore we need to keep track of what it touches
* and only clean those before the next execution.
*/
@Override
public boolean isSpannerReachable(V s, V t, double hops)
{
// initialize distances and queue
while (!touchedVertices.isEmpty()) {
V u = touchedVertices.pop();
vertexDistance.put(u, Integer.MAX_VALUE);
}
while (!queue.isEmpty()) {
queue.pop();
}
// do BFS
touchedVertices.push(s);
queue.push(s);
vertexDistance.put(s, 0);
while (!queue.isEmpty()) {
V u = queue.pop();
Integer uDistance = vertexDistance.get(u);
if (u.equals(t)) { // found
return uDistance <= hops;
}
for (E e : spanner.edgesOf(u)) {
V v = Graphs.getOppositeVertex(spanner, e, u);
Integer vDistance = vertexDistance.get(v);
if (vDistance == Integer.MAX_VALUE) {
touchedVertices.push(v);
vertexDistance.put(v, uDistance + 1);
queue.push(v);
}
}
}
return false;
}
@Override
public void addSpannerEdge(V s, V t, double weight)
{
spanner.addEdge(s, t);
}
}
private class WeightedSpannerAlgorithm
extends SpannerAlgorithmBase
{
protected WeightedGraph spanner;
protected FibonacciHeap heap;
protected Map> nodes;
public WeightedSpannerAlgorithm()
{
this.spanner = new SimpleWeightedGraph(graph.getEdgeFactory());
for (V v : graph.vertexSet()) {
spanner.addVertex(v);
}
this.heap = new FibonacciHeap();
this.nodes = new LinkedHashMap>();
}
@Override
public boolean isSpannerReachable(V s, V t, double distance)
{
// init
heap.clear();
nodes.clear();
FibonacciHeapNode sNode = new FibonacciHeapNode(s);
nodes.put(s, sNode);
heap.insert(sNode, 0d);
while (!heap.isEmpty()) {
FibonacciHeapNode uNode = heap.removeMin();
double uDistance = uNode.getKey();
V u = uNode.getData();
if (uDistance > distance) {
return false;
}
if (u.equals(t)) { // found
return true;
}
for (E e : spanner.edgesOf(u)) {
V v = Graphs.getOppositeVertex(spanner, e, u);
FibonacciHeapNode vNode = nodes.get(v);
double vDistance = uDistance + spanner.getEdgeWeight(e);
if (vNode == null) { // no distance
vNode = new FibonacciHeapNode(v);
nodes.put(v, vNode);
heap.insert(vNode, vDistance);
} else if (vDistance < vNode.getKey()) {
heap.decreaseKey(vNode, vDistance);
}
}
}
return false;
}
@Override
public void addSpannerEdge(V s, V t, double weight)
{
Graphs.addEdge(spanner, s, t, weight);
}
}
}