org.jgrapht.alg.GreedyMultiplicativeSpanner Maven / Gradle / Ivy
/*
* (C) Copyright 2016-2016, 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 org.jgrapht.alg;
import java.util.*;
import org.jgrapht.*;
import org.jgrapht.graph.*;
import org.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
{
private final Set edgeList;
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 a graph.
*
* @param graph the input graph
* @param k positive integer.
*/
public GreedyMultiplicativeSpanner(UndirectedGraph graph, int k)
{
this.graph = graph;
this.edgeList = new LinkedHashSet<>();
if (k <= 0) {
throw new IllegalArgumentException(
"k should be positive in (2k-1)-spanner construction");
}
this.k = Math.min(k, MAX_K);
if (graph instanceof WeightedGraph) {
new WeightedSpannerAlgorithm().run();
} else {
new UnweightedSpannerAlgorithm().run();
}
}
/**
* Get the edge set of the spanner.
*
* @return the set of edges of the spanner
*/
public Set getSpannerEdgeSet()
{
return edgeList;
}
// 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 void 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
for (E e : allEdges) {
V s = graph.getEdgeSource(e);
V t = graph.getEdgeTarget(e);
if (!s.equals(t)) { // self-loop?
double distance = (2 * k - 1) * graph.getEdgeWeight(e);
if (!isSpannerReachable(s, t, distance)) {
edgeList.add(e);
addSpannerEdge(s, t, graph.getEdgeWeight(e));
}
}
}
}
}
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<>();
}
@Override
public void addSpannerEdge(V s, V t, double weight)
{
spanner.addEdge(s, t);
}
/**
* 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;
}
}
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);
}
}
}