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

org.psjava.algo.graph.shortestpath.NegativeCycleFinder Maven / Gradle / Ivy

The newest version!
package org.psjava.algo.graph.shortestpath;

import org.psjava.ds.Collection;
import org.psjava.ds.deque.DoubleLinkedList;
import org.psjava.ds.graph.AllEdgeInGraph;
import org.psjava.ds.graph.DirectedWeightedEdge;
import org.psjava.ds.graph.Graph;
import org.psjava.ds.graph.MutableDirectedGraph;
import org.psjava.ds.numbersystrem.AddableNumberSystem;
import org.psjava.ds.numbersystrem.InfinitableAddableNumberSystem;
import org.psjava.ds.set.MutableSet;
import org.psjava.goods.GoodMutableSetFactory;
import org.psjava.util.AssertStatus;

/** Finds any one of possible negative cycles in a graph. */
public class NegativeCycleFinder {

	private static class AugmentedEdge> implements DirectedWeightedEdge {
		private final Object from;
		private final Object to;
		private final W weight;
		private final E originalOrNull; // to track original graph's path

		AugmentedEdge(Object from, Object to, W w, E originalOrNull) {
			this.from = from;
			this.to = to;
			this.weight = w;
			this.originalOrNull = originalOrNull;
		}

		@Override
		public Object from() {
			return from;
		}

		@Override
		public Object to() {
			return to;
		}

		@Override
		public W weight() {
			return weight;
		}

		public E getOriginal() {
			AssertStatus.assertTrue(originalOrNull != null);
			return originalOrNull;
		}
	}

	private static final Object VIRTUAL_START = new Object();

	public static > NegativeCycleFinderResult find(Graph graph, AddableNumberSystem weightSystem) {
		Graph> augmented = augment(graph, weightSystem);
		InfinitableAddableNumberSystem ns = InfinitableAddableNumberSystem.wrap(weightSystem);
		SingleSourceShortestPathCalcStatus> bellmanFordStatus = BellmanFordAlgorithm.createInitialStatus(augmented, VIRTUAL_START, ns);
		BellmanFordAlgorithm.relaxEnough(augmented, bellmanFordStatus, ns);
		AugmentedEdge relaxed = relaxAnyEdgeIfPossible(augmented, ns, bellmanFordStatus);
		return createResult(bellmanFordStatus, relaxed);
	}

	private static > Graph> augment(Graph original, AddableNumberSystem ns) {
		MutableDirectedGraph> r = MutableDirectedGraph.create();
		for (V v : original.getVertices())
			r.insertVertex(v);
		for (E e : AllEdgeInGraph.wrap(original))
			r.addEdge(new AugmentedEdge(e.from(), e.to(), e.weight(), e));
		r.insertVertex(VIRTUAL_START);
		for (V v : original.getVertices())
			r.addEdge(new AugmentedEdge(VIRTUAL_START, v, ns.getZero(), null));
		return r;
	}

	private static > AugmentedEdge relaxAnyEdgeIfPossible(Graph> graph, InfinitableAddableNumberSystem ns,
			SingleSourceShortestPathCalcStatus> status) {
		for (AugmentedEdge e : AllEdgeInGraph.wrap(graph))
			if (Relax.relax(status.distance, status.previous, e, ns))
				return e;
		return null;
	}

	private static > NegativeCycleFinderResult createResult(final SingleSourceShortestPathCalcStatus> status,
			final AugmentedEdge lastRelaxedEdgeOrNull) {
		return new NegativeCycleFinderResult() {
			@Override
			public boolean hasCycle() {
				return lastRelaxedEdgeOrNull != null;
			}

			@Override
			public Collection getPath() {
				AssertStatus.assertTrue(hasCycle(), "no cycle");
				MutableSet visited = GoodMutableSetFactory.getInstance().create();
				DoubleLinkedList path = DoubleLinkedList.create();
				AugmentedEdge curEdge = lastRelaxedEdgeOrNull;
				while (true) {
					path.addToFirst(curEdge.getOriginal());
					visited.add(curEdge.to());
					if (visited.contains(curEdge.from()))
						break;
					curEdge = status.previous.get(curEdge.from());
				}
				while (!path.getLast().to().equals(curEdge.from()))
					path.removeLast();
				return path;
			}
		};
	}

	private NegativeCycleFinder() {
	}

}