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

org.jgrapht.alg.spanning.GreedyMultiplicativeSpanner Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2016-2023, by Dimitrios Michail 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.alg.spanning;

import org.jgrapht.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.graph.*;
import org.jgrapht.graph.builder.*;
import org.jgrapht.util.*;
import org.jheaps.*;
import org.jheaps.tree.*;

import java.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} + n \log n))$ 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 */ public class GreedyMultiplicativeSpanner implements SpannerAlgorithm { private final Graph 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. * * @throws IllegalArgumentException if the graph is not undirected * @throws IllegalArgumentException if k is not positive */ public GreedyMultiplicativeSpanner(Graph graph, int k) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); if (!graph.getType().isUndirected()) { throw new IllegalArgumentException("graph is not undirected"); } 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.getType().isWeighted()) { 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()); allEdges.sort(Comparator.comparingDouble(graph::getEdgeWeight)); // 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 Graph spanner; protected Map vertexDistance; protected Deque queue; protected Deque touchedVertices; public UnweightedSpannerAlgorithm() { spanner = GraphTypeBuilder . undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .edgeSupplier(graph.getEdgeSupplier()).buildGraph(); touchedVertices = new ArrayDeque<>(graph.vertexSet().size()); for (V v : graph.vertexSet()) { spanner.addVertex(v); touchedVertices.push(v); } vertexDistance = CollectionUtil.newHashMapWithExpectedSize(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 Graph spanner; protected AddressableHeap heap; protected Map> nodes; public WeightedSpannerAlgorithm() { this.spanner = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); for (V v : graph.vertexSet()) { spanner.addVertex(v); } this.heap = new PairingHeap<>(); this.nodes = new LinkedHashMap<>(); } @Override public boolean isSpannerReachable(V s, V t, double distance) { // init heap.clear(); nodes.clear(); AddressableHeap.Handle sNode = heap.insert(0d, s); nodes.put(s, sNode); while (!heap.isEmpty()) { AddressableHeap.Handle uNode = heap.deleteMin(); double uDistance = uNode.getKey(); V u = uNode.getValue(); if (uDistance > distance) { return false; } if (u.equals(t)) { // found return true; } for (DefaultWeightedEdge e : spanner.edgesOf(u)) { V v = Graphs.getOppositeVertex(spanner, e, u); AddressableHeap.Handle vNode = nodes.get(v); double vDistance = uDistance + spanner.getEdgeWeight(e); if (vNode == null) { // no distance vNode = heap.insert(vDistance, v); nodes.put(v, vNode); } else if (vDistance < vNode.getKey()) { vNode.decreaseKey(vDistance); } } } return false; } @Override public void addSpannerEdge(V s, V t, double weight) { Graphs.addEdge(spanner, s, t, weight); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy