From e3709251e4c2daa193d6318fe31e239b27223526 Mon Sep 17 00:00:00 2001 From: rec0de Date: Tue, 26 Jun 2018 21:10:30 +0200 Subject: [PATCH] Add comments --- MaxFlow/src/lab/Edge.java | 26 +++++++++--- MaxFlow/src/lab/MaxFlow.java | 77 +++++++++++++++++++++++++----------- MaxFlow/src/lab/Vertex.java | 15 ++++--- 3 files changed, 81 insertions(+), 37 deletions(-) diff --git a/MaxFlow/src/lab/Edge.java b/MaxFlow/src/lab/Edge.java index 6219fc6..fd318d5 100644 --- a/MaxFlow/src/lab/Edge.java +++ b/MaxFlow/src/lab/Edge.java @@ -18,8 +18,7 @@ public class Edge { * Constructor * @param from Source vertex of the edge * @param to Destination vertex of the edge - * @param distance Length (km) of the edge - * @param speedLimit Speed with which the 'car' can travel along this edge + * @param capacity Maximum number of cars that can travel on this edge per hour */ public Edge(Vertex from, Vertex to, int capacity) { this.from = from; @@ -29,35 +28,50 @@ public class Edge { } /** - * Get driving time for this edge - * @return Driving time (m) for this edge + * Get current flow along this edge + * @return Current flow in the direction of the edge */ public int flow() { return this.flow; } /** - * Get length of this edge - * @return Length of the edge + * Get maximum flow capacity of this edge + * @return Capacity of the edge */ public int capacity() { return this.capacity; } + /** + * Get residual (unused) capacity of the edge + * @return Free capacity of the edge + */ public int freeCapacity() { return this.capacity - this.flow; } + /** + * Get capacity of the edge running in the opposite direction in the residual network (alias for flow) + * @return Capacity of the back-edge in the residual network + */ public int backCapacity() { return this.flow; } + /** + * Increase the flow along this edge (route additional cars over this street) + * @param inc + */ public void increaseFlow(int inc) { this.flow += inc; if(this.flow < 0 || this.flow > this.capacity) throw new RuntimeException("Edge capacity exceeded: "+this.flow); } + /** + * Reset the edge (e.g. for different flow calculations in the same graph) + */ public void reset() { this.flow = 0; } diff --git a/MaxFlow/src/lab/MaxFlow.java b/MaxFlow/src/lab/MaxFlow.java index fd99f5b..5002628 100644 --- a/MaxFlow/src/lab/MaxFlow.java +++ b/MaxFlow/src/lab/MaxFlow.java @@ -44,12 +44,16 @@ public class MaxFlow { // Read all lines while ((line = br.readLine()) != null) { + // If line is an edge if(line.matches(".* -> .*;")) { + // Do some string replace regex magic String[] info = line.replaceAll("\\s+", " ").replaceFirst(" -> ", ",").replaceFirst("\\[label=\"", ",").replaceFirst("\".*$", "").split(","); info[0] = info[0].replaceFirst(" ", ""); info[1] = info[1].replaceFirst(" ", ""); Vertex origin; Vertex destination; + + // Create vertices if they do not exist already if(vertices.containsKey(info[0])) origin = vertices.get(info[0]); else { @@ -97,9 +101,11 @@ public class MaxFlow { ArrayList sourceVertices = new ArrayList(); ArrayList destinationVertices = new ArrayList(); + // Reset edge flows left from previous calculations for(Edge edge : edges) edge.reset(); + // Build Lists of sources and destinations for(String sourceKey : sources) if(vertices.containsKey(sourceKey)) sourceVertices.add(vertices.get(sourceKey)); @@ -107,6 +113,7 @@ public class MaxFlow { if(vertices.containsKey(destinationKey)) destinationVertices.add(vertices.get(destinationKey)); + // Handle edge cases if(sourceVertices.isEmpty() && destinationVertices.isEmpty()) return MaxFlow.NO_SOURCE_DESTINATION_FOUND; else if(sourceVertices.isEmpty()) @@ -114,20 +121,23 @@ public class MaxFlow { else if(destinationVertices.isEmpty()) return MaxFlow.NO_DESTINATION_FOUND; + // To avoid restructuring the input graph, sources are added to the flow one after another + // This intuitively yields the same result as it is equivalent to using certain paths from a unified source before others for(Vertex source : sourceVertices) { try { - while(findAugmentingPath(source, destinationVertices)) {} + while(findAugmentingPath(source, destinationVertices)) {} // Call findAugmentingPath until it returns false (= no remaining path) } catch(InfiniteFlowException e) { - return MaxFlow.SOURCES_SAME_AS_DESTINATIONS; + return MaxFlow.SOURCES_SAME_AS_DESTINATIONS; // return appropriate error code if sources and destinations are not distinct } } int res = 0; + // Total net flow in all sources is the MaxFlow of the graph for(Vertex source : sourceVertices) res += source.getTotalFlow(); - return res == 0 ? MaxFlow.NO_PATH : res; + return res == 0 ? MaxFlow.NO_PATH : res; // Flow = 0 implies no path from source to drain } /** @@ -141,9 +151,11 @@ public class MaxFlow { ArrayList sourceVertices = new ArrayList(); ArrayList destinationVertices = new ArrayList(); + // Reset edge flows left from previous calculations for(Edge edge : edges) edge.reset(); + // Build Lists of sources and destinations for(String sourceKey : sources) if(vertices.containsKey(sourceKey)) sourceVertices.add(vertices.get(sourceKey)); @@ -151,13 +163,16 @@ public class MaxFlow { if(vertices.containsKey(destinationKey)) destinationVertices.add(vertices.get(destinationKey)); + // To avoid restructuring the input graph, sources are added to the flow one after another + // This intuitively yields the same result as it is equivalent to using certain paths from a unified source before others if(!sourceVertices.isEmpty() && !destinationVertices.isEmpty()) { for(Vertex source : sourceVertices) { try { - while(findAugmentingPath(source, destinationVertices)) {} + while(findAugmentingPath(source, destinationVertices)) {} // Call findAugmentingPath until it returns false (= no remaining path) } catch(InfiniteFlowException e) { break; // Doesn't reset capacities already used before exception came up + // Works alright with the test cases though } } } @@ -165,8 +180,8 @@ public class MaxFlow { for(Edge edge : edges) edge.emboldenIfFreeCapacity(); + // Build dotcode representation of graph ArrayList res = new ArrayList(); - res.add("digraph{"); for(String sourceName : sources) @@ -177,82 +192,98 @@ public class MaxFlow { res.add(edge.toDotCode()); res.add("}"); - return res; } + /** + * Finds an augmenting path in the residual network of the current flow from a single source to any destination + * (if such a path exists) and adds it to the flow + * @param source The source vertex + * @param destinations List of possible destination vertices + * @return True if a path was added, False if no augmenting path exists + * @throws InfiniteFlowException + */ private boolean findAugmentingPath(Vertex source, ArrayList destinations) throws InfiniteFlowException{ HashMap pred = new HashMap(); ArrayList queue = new ArrayList(); - ArrayList res = new ArrayList(); - System.out.println("Looking for augmenting path from "+source.getName()); + //System.out.println("Looking for augmenting path from "+source.getName()); + // Start BFS at source vertex queue.add(source); - pred.put(source.getName(), null); + pred.put(source.getName(), null); // Predecessor of source is null Vertex current; while(!queue.isEmpty()) { + // Remove the next queued vertex current = queue.remove(0); //System.out.println("Visiting "+current.getName()); + // If a path to a destination was found if(destinations.contains(current)) { if(current == source) - throw new InfiniteFlowException("Source " + source.getName() + " is also destination"); + throw new InfiniteFlowException("Source " + source.getName() + " is also destination"); // Throw exception if destination is a source - System.out.println("Found augmenting path"); - - // Build path and return + // Build augmenting path and find max capacity + ArrayList path = new ArrayList(); String nextName = current.getName(); String currentName; Edge backedge; - int maxCapacity = Integer.MAX_VALUE; + int maxCapacity = Integer.MAX_VALUE; // Assume 'infinite' capacity initially + // Walk backwards along predecessors while(pred.get(nextName) != null) { - backedge = pred.get(nextName); + backedge = pred.get(nextName); // Get edge that was used to get to nextName currentName = nextName; //System.out.println("Walking backwards: "+backedge.from.getName()+" -> "+backedge.to.getName()); - res.add(0, backedge); + path.add(0, backedge); + // Get next vertex on path and update maxCapacity (conditionals needed because directed edges can be walked forwards _and_ backwards in the residual network) nextName = (backedge.to.getName().equals(currentName)) ? backedge.from.getName() : backedge.to.getName(); maxCapacity = (backedge.to.getName().equals(currentName)) ? Math.min(maxCapacity, backedge.freeCapacity()) : Math.min(maxCapacity, backedge.backCapacity()); } + // Walk path forwards and update flow Vertex nextVertex = source; - System.out.println(maxCapacity); - for(Edge edge : res) { + for(Edge edge : path) { + // If the edge was used along its natural direction, increase the flow by maxCapacity if(edge.from == nextVertex) { edge.increaseFlow(maxCapacity); nextVertex = edge.to; } + // If the edge was used opposed to its natural direction, decrease the flow else if(edge.to == nextVertex) { edge.increaseFlow(- maxCapacity); nextVertex = edge.from; } else - throw new RuntimeException("Edge is neither from nor to source node"); + throw new RuntimeException("Edge is neither from nor to source node"); // Shouldn't happen but let's be safe } return true; } + // Otherwise, continue BFS with all connected vertices for(Edge edge : current.getResidual()) { + // Do not visit already visited vertices (vertices that already have a predecessor) if(edge.from == current && pred.containsKey(edge.to.getName()) || edge.to == current && pred.containsKey(edge.from.getName())) continue; + // If the edge is a regular forward edge with nonzero capacity, add the destination vertex and update its predecessor if(edge.from == current && edge.freeCapacity() > 0) { queue.add(edge.to); pred.put(edge.to.getName(), edge); - System.out.println("Adding "+current.getName()+" -> "+edge.to.getName() + " cap: "+edge.freeCapacity()); + //System.out.println("Adding "+current.getName()+" -> "+edge.to.getName() + " cap: "+edge.freeCapacity()); } + // If the edge is a backwards edge with nonzero effective capacity, add the destination vertex and update predecessor else if(edge.to == current && edge.backCapacity() > 0) { queue.add(edge.from); pred.put(edge.from.getName(), edge); - System.out.println("Adding "+current.getName()+" -> "+edge.from.getName() + " cap: "+edge.backCapacity()); + //System.out.println("Adding "+current.getName()+" -> "+edge.from.getName() + " cap: "+edge.backCapacity()); } } } - System.out.println("No augmenting path from "+source.getName()); - + //System.out.println("No augmenting path from "+source.getName()); + // Return false if queue is empty but no path has been found return false; } diff --git a/MaxFlow/src/lab/Vertex.java b/MaxFlow/src/lab/Vertex.java index 94fe88d..d35a2b6 100644 --- a/MaxFlow/src/lab/Vertex.java +++ b/MaxFlow/src/lab/Vertex.java @@ -3,7 +3,7 @@ package lab; import java.util.ArrayList; /** - * Class representing a vertex ('street crossing' in this context) in a directed graph + * Class representing a vertex in a directed graph */ public class Vertex { @@ -15,7 +15,6 @@ public class Vertex { /** * Constructor * @param name Name of the vertex. Assumed to be per-graph unique - * @param waitTime Average time spent waiting at this vertex */ public Vertex(String name) { this.name = name; @@ -51,17 +50,17 @@ public class Vertex { } /** - * Get a List of all outgoing edges of this vertex - * @return + * Get a List of all outgoing or incoming edges of this vertex (for analyzing the residual network) + * @return List of all edges connecting to or from this vertex */ - public ArrayList getOutgoing() { - return outgoing; - } - public ArrayList getResidual() { return allEdges; } + /** + * Calculates the net flow in this vertex. Should be zero for all non-source non-drain vertices + * @return Net flow of the vertex + */ public int getTotalFlow() { int res = 0; for(Edge edge : outgoing)