/* ========================================== * JGraphT : a free Java graph-theory library * ========================================== * * Project Info: http://jgrapht.sourceforge.net/ * Project Creator: Barak Naveh (http://sourceforge.net/users/barak_naveh) * * (C) Copyright 2003-2011, by Barak Naveh and Contributors. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, * Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ /* ---------------- * StoerWagnerMinimumCut.java * ---------------- * (C) Copyright 2011-2011, by Robby McKilliam and Contributors. * * Original Author: Robby McKilliam * Contributor(s): - * * $Id: StoerWagnerMinimumCut.java $ * * Changes * ------- * */ package edu.nd.nina.alg; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.Set; import edu.nd.nina.WeightedGraph; import edu.nd.nina.graph.DefaultWeightedEdge; import edu.nd.nina.graph.SimpleWeightedGraph; /** * Implements the <a href="http://dl.acm.org/citation.cfm?id=263872">Stoer and * Wagner minimum cut algorithm</a>. Deterministically computes the minimum cut * in O(|V||E| + |V|log|V|) time. This implementation uses Java's PriorityQueue * and requires O(|V||E|log|E|) time. M. Stoer and F. Wagner, "A Simple Min-Cut * Algorithm", Journal of the ACM, volume 44, number 4. pp 585-591, 1997. * * @author Robby McKilliam */ public class StoerWagnerMinimumCut<V, E> { //~ Instance fields -------------------------------------------------------- final WeightedGraph<Set<V>, DefaultWeightedEdge> workingGraph; double bestcutweight = Double.POSITIVE_INFINITY; Set<V> bestCut; boolean firstRun = true; //~ Constructors ----------------------------------------------------------- /** * Will compute the minimum cut in graph. * * @param graph graph over which to run algorithm */ public StoerWagnerMinimumCut(WeightedGraph<V, E> graph) { //get a version of this graph where each vertex is wrapped with a list workingGraph = new SimpleWeightedGraph<Set<V>, DefaultWeightedEdge>( DefaultWeightedEdge.class); Map<V, Set<V>> vertexMap = new HashMap<V, Set<V>>(); for (V v : graph.vertexSet()) { Set<V> list = new HashSet<V>(); list.add(v); vertexMap.put(v, list); workingGraph.addVertex(list); } for (E e : graph.edgeSet()) { V s = graph.getEdgeSource(e); Set<V> sNew = vertexMap.get(s); V t = graph.getEdgeTarget(e); Set<V> tNew = vertexMap.get(t); DefaultWeightedEdge eNew = workingGraph.addEdge(sNew, tNew); workingGraph.setEdgeWeight(eNew, graph.getEdgeWeight(e)); } //arbitrary vertex used to seed the algorithm. Set<V> a = workingGraph.vertexSet().iterator().next(); while (workingGraph.vertexSet().size() > 2) { minimumCutPhase(a); } } //~ Methods ---------------------------------------------------------------- /** * Implements the MinimumCutPhase function of Stoer and Wagner */ protected void minimumCutPhase(Set<V> a) { //construct sorted queue with vertices connected to vertex a PriorityQueue<VertexAndWeight> queue = new PriorityQueue<VertexAndWeight>(); Map<Set<V>, VertexAndWeight> dmap = new HashMap<Set<V>, VertexAndWeight>(); for (Set<V> v : workingGraph.vertexSet()) { if (v != a) { Double w = -workingGraph.getEdgeWeight(workingGraph.getEdge(v, a)); VertexAndWeight vandw = new VertexAndWeight(v, w); queue.add(vandw); dmap.put(v, vandw); } } //now iteratatively update the queue to get the required vertex ordering List<Set<V>> list = new ArrayList<Set<V>>(workingGraph.vertexSet().size()); list.add(a); while (!queue.isEmpty()) { Set<V> v = queue.poll().vertex; dmap.remove(v); list.add(v); for (DefaultWeightedEdge e : workingGraph.edgesOf(v)) { Set<V> vc; if (v != workingGraph.getEdgeSource(e)) { vc = workingGraph.getEdgeSource(e); } else { vc = workingGraph.getEdgeTarget(e); } if (dmap.get(vc) != null) { Double neww = -workingGraph.getEdgeWeight(workingGraph.getEdge(v, vc)) + dmap.get(vc).weight; queue.remove(dmap.get(vc)); //this is O(logn) but could be //O(1)? dmap.get(vc).weight = neww; queue.add(dmap.get(vc)); //this is O(logn) but could be //O(1)? } } } //if this is the first run we compute the weight of last vertex in the //list if (firstRun) { Set<V> v = list.get(list.size() - 1); double w = vertexWeight(v); if (w < bestcutweight) { bestcutweight = w; bestCut = v; } firstRun = false; } //the last two elements in list are the vertices we want to merge. Set<V> s = list.get(list.size() - 2); Set<V> t = list.get(list.size() - 1); //merge these vertices and get the weight. VertexAndWeight vw = mergeVertices(s, t); //If this is the best cut so far store it. if (vw.weight < bestcutweight) { bestcutweight = vw.weight; bestCut = vw.vertex; } } /** * Return the weight of the minimum cut */ public double minCutWeight() { return bestcutweight; } /** * Return a set of vertices on one side of the cut */ public Set<V> minCut() { return bestCut; } /** * Merges vertex t into vertex s, summing the weights as required. Returns * the merged vertex and the sum of its weights */ protected VertexAndWeight mergeVertices(Set<V> s, Set<V> t) { //construct the new combinedvertex Set<V> set = new HashSet<V>(); for (V v : s) { set.add(v); } for (V v : t) { set.add(v); } workingGraph.addVertex(set); //add edges and weights to the combined vertex double wsum = 0.0; for (Set<V> v : workingGraph.vertexSet()) { if ((s != v) && (t != v)) { DefaultWeightedEdge etv = workingGraph.getEdge(t, v); DefaultWeightedEdge esv = workingGraph.getEdge(s, v); double wtv = 0.0, wsv = 0.0; if (etv != null) { wtv = workingGraph.getEdgeWeight(etv); } if (esv != null) { wsv = workingGraph.getEdgeWeight(esv); } double neww = wtv + wsv; wsum += neww; if (neww != 0.0) { workingGraph.setEdgeWeight( workingGraph.addEdge(set, v), neww); } } } //remove original vertices workingGraph.removeVertex(t); workingGraph.removeVertex(s); return new VertexAndWeight(set, wsum); } /** * Compute the sum of the weights entering a vertex */ public double vertexWeight(Set<V> v) { double wsum = 0.0; for (DefaultWeightedEdge e : workingGraph.edgesOf(v)) { wsum += workingGraph.getEdgeWeight(e); } return wsum; } //~ Inner Classes ---------------------------------------------------------- /** * Class for weighted vertices */ protected class VertexAndWeight implements Comparable<VertexAndWeight> { public Set<V> vertex; public Double weight; public VertexAndWeight(Set<V> v, double w) { this.vertex = v; this.weight = w; } @Override public int compareTo(VertexAndWeight that) { return Double.compare(weight, that.weight); } @Override public String toString() { return "(" + vertex + ", " + weight + ")"; } } } // End StoerWagnerMinimumCut.java