diff --git a/MaxFlow/.classpath b/MaxFlow/.classpath new file mode 100644 index 0000000..539dba0 --- /dev/null +++ b/MaxFlow/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/MaxFlow/.gitignore b/MaxFlow/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/MaxFlow/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/MaxFlow/.project b/MaxFlow/.project new file mode 100644 index 0000000..0cddd73 --- /dev/null +++ b/MaxFlow/.project @@ -0,0 +1,17 @@ + + + MaxFlow + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/MaxFlow/src/lab/Edge.java b/MaxFlow/src/lab/Edge.java new file mode 100644 index 0000000..6219fc6 --- /dev/null +++ b/MaxFlow/src/lab/Edge.java @@ -0,0 +1,79 @@ +/** + * + */ +package lab; + +/** + * Class representing an edge in a directed graph + */ +public class Edge { + + protected final Vertex from; + protected final Vertex to; + private final int capacity; + private int flow; + private boolean bold = false; + + /** + * 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 + */ + public Edge(Vertex from, Vertex to, int capacity) { + this.from = from; + this.to = to; + this.capacity = capacity; + this.flow = 0; + } + + /** + * Get driving time for this edge + * @return Driving time (m) for this edge + */ + public int flow() { + return this.flow; + } + + /** + * Get length of this edge + * @return Length of the edge + */ + public int capacity() { + return this.capacity; + } + + public int freeCapacity() { + return this.capacity - this.flow; + } + + public int backCapacity() { + return this.flow; + } + + public void increaseFlow(int inc) { + this.flow += inc; + if(this.flow < 0 || this.flow > this.capacity) + throw new RuntimeException("Edge capacity exceeded: "+this.flow); + } + + public void reset() { + this.flow = 0; + } + + /** + * Embolden the edge if it's capacity was not fully utilized + */ + public void emboldenIfFreeCapacity() { + this.bold = this.freeCapacity() > 0; + } + + /** + * Get a dot code representation of this edge + * @return Dot code line representing the edge + */ + public String toDotCode() { + return from.getName() + " -> " + to.getName() + " [label=\""+ this.capacity + "-" + this.flow + "\"]" + (this.bold ? "[style=bold];" : ";"); + } +} diff --git a/MaxFlow/src/lab/InfiniteFlowException.java b/MaxFlow/src/lab/InfiniteFlowException.java new file mode 100644 index 0000000..4879564 --- /dev/null +++ b/MaxFlow/src/lab/InfiniteFlowException.java @@ -0,0 +1,18 @@ +/** + * + */ +package lab; + +/** + * Exception signaling that a given graph has infinite maximum flow because at + * least one source and destination are equal + */ +public class InfiniteFlowException extends Exception { + + public InfiniteFlowException(String string) { + super(string); + } + + private static final long serialVersionUID = -2738022894329680602L; + +} diff --git a/MaxFlow/src/lab/MaxFlow.java b/MaxFlow/src/lab/MaxFlow.java index 4f2ed05..fd99f5b 100644 --- a/MaxFlow/src/lab/MaxFlow.java +++ b/MaxFlow/src/lab/MaxFlow.java @@ -1,6 +1,12 @@ package lab; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; /** * MaxFlow.java */ @@ -21,14 +27,57 @@ public class MaxFlow { public static final int NO_PATH = -4; public static final int SOURCES_SAME_AS_DESTINATIONS = Integer.MAX_VALUE; + private HashMap vertices; + private ArrayList edges; + /** * The constructor, setting the name of the file to parse. * * @param filename the absolute or relative path and filename of the file */ public MaxFlow(final String filename) { - //TODO Add you code here + this.vertices = new HashMap(); + this.edges = new ArrayList(); + try (BufferedReader br = new BufferedReader(new FileReader(filename))) { + String line; + + // Read all lines + while ((line = br.readLine()) != null) { + if(line.matches(".* -> .*;")) { + String[] info = line.replaceAll("\\s+", " ").replaceFirst(" -> ", ",").replaceFirst("\\[label=\"", ",").replaceFirst("\".*$", "").split(","); + info[0] = info[0].replaceFirst(" ", ""); + info[1] = info[1].replaceFirst(" ", ""); + Vertex origin; + Vertex destination; + if(vertices.containsKey(info[0])) + origin = vertices.get(info[0]); + else { + origin = new Vertex(info[0]); + this.vertices.put(info[0], origin); + } + + if(vertices.containsKey(info[1])) + destination = vertices.get(info[1]); + else { + destination = new Vertex(info[1]); + this.vertices.put(info[1], destination); + } + + Edge edge = new Edge(origin, destination, Integer.parseInt(info[2])); + // Add edge to source and destination vertex + origin.addOutgoing(edge); + destination.addIncoming(edge); + this.edges.add(edge); + //System.out.println("Parse edge "+info[0]+"->"+info[1]+" cap: "+info[2]); + } + } + + } catch (FileNotFoundException e) { + System.out.println("File not found"); + } catch (IOException e) { + System.out.println("IO Exception"); + } } /** @@ -45,8 +94,40 @@ public class MaxFlow { * SOURCES_SAME_AS_DESTINATIONS if sources == destinations */ public final int findMaxFlow(final String[] sources, final String[] destinations) { - //TODO Add you code here - return 0; // dummy, replace + ArrayList sourceVertices = new ArrayList(); + ArrayList destinationVertices = new ArrayList(); + + for(Edge edge : edges) + edge.reset(); + + for(String sourceKey : sources) + if(vertices.containsKey(sourceKey)) + sourceVertices.add(vertices.get(sourceKey)); + for(String destinationKey : destinations) + if(vertices.containsKey(destinationKey)) + destinationVertices.add(vertices.get(destinationKey)); + + if(sourceVertices.isEmpty() && destinationVertices.isEmpty()) + return MaxFlow.NO_SOURCE_DESTINATION_FOUND; + else if(sourceVertices.isEmpty()) + return MaxFlow.NO_SOURCE_FOUND; + else if(destinationVertices.isEmpty()) + return MaxFlow.NO_DESTINATION_FOUND; + + for(Vertex source : sourceVertices) { + try { + while(findAugmentingPath(source, destinationVertices)) {} + } + catch(InfiniteFlowException e) { + return MaxFlow.SOURCES_SAME_AS_DESTINATIONS; + } + } + + int res = 0; + for(Vertex source : sourceVertices) + res += source.getTotalFlow(); + + return res == 0 ? MaxFlow.NO_PATH : res; } /** @@ -57,8 +138,122 @@ public class MaxFlow { * @return a ArrayList of Strings as specified in the task in dot code */ public final ArrayList findResidualNetwork(final String[] sources, final String[] destinations) { - //TODO Add you code here - return null; // dummy, replace + ArrayList sourceVertices = new ArrayList(); + ArrayList destinationVertices = new ArrayList(); + + for(Edge edge : edges) + edge.reset(); + + for(String sourceKey : sources) + if(vertices.containsKey(sourceKey)) + sourceVertices.add(vertices.get(sourceKey)); + for(String destinationKey : destinations) + if(vertices.containsKey(destinationKey)) + destinationVertices.add(vertices.get(destinationKey)); + + if(!sourceVertices.isEmpty() && !destinationVertices.isEmpty()) { + for(Vertex source : sourceVertices) { + try { + while(findAugmentingPath(source, destinationVertices)) {} + } + catch(InfiniteFlowException e) { + break; // Doesn't reset capacities already used before exception came up + } + } + } + + for(Edge edge : edges) + edge.emboldenIfFreeCapacity(); + + ArrayList res = new ArrayList(); + + res.add("digraph{"); + + for(String sourceName : sources) + res.add(sourceName+" [shape=doublecircle][style=bold];"); + for(String destName : destinations) + res.add(destName+" [shape=circle][style=bold];"); + for(Edge edge : this.edges) + res.add(edge.toDotCode()); + + res.add("}"); + + return res; + } + + 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()); + + queue.add(source); + pred.put(source.getName(), null); + + Vertex current; + while(!queue.isEmpty()) { + current = queue.remove(0); + + //System.out.println("Visiting "+current.getName()); + + if(destinations.contains(current)) { + if(current == source) + throw new InfiniteFlowException("Source " + source.getName() + " is also destination"); + + System.out.println("Found augmenting path"); + + // Build path and return + String nextName = current.getName(); + String currentName; + Edge backedge; + int maxCapacity = Integer.MAX_VALUE; + while(pred.get(nextName) != null) { + backedge = pred.get(nextName); + currentName = nextName; + //System.out.println("Walking backwards: "+backedge.from.getName()+" -> "+backedge.to.getName()); + res.add(0, backedge); + 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()); + } + Vertex nextVertex = source; + System.out.println(maxCapacity); + for(Edge edge : res) { + if(edge.from == nextVertex) { + edge.increaseFlow(maxCapacity); + nextVertex = edge.to; + } + else if(edge.to == nextVertex) { + edge.increaseFlow(- maxCapacity); + nextVertex = edge.from; + } + else + throw new RuntimeException("Edge is neither from nor to source node"); + } + + return true; + } + + for(Edge edge : current.getResidual()) { + if(edge.from == current && pred.containsKey(edge.to.getName()) || edge.to == current && pred.containsKey(edge.from.getName())) + continue; + + 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()); + } + 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("No augmenting path from "+source.getName()); + + return false; } } \ No newline at end of file diff --git a/MaxFlow/src/lab/Vertex.java b/MaxFlow/src/lab/Vertex.java new file mode 100644 index 0000000..94fe88d --- /dev/null +++ b/MaxFlow/src/lab/Vertex.java @@ -0,0 +1,81 @@ +package lab; + +import java.util.ArrayList; + +/** + * Class representing a vertex ('street crossing' in this context) in a directed graph + */ +public class Vertex { + + private final String name; + private ArrayList incoming; + private ArrayList outgoing; + private ArrayList allEdges; + + /** + * 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; + this.incoming = new ArrayList(); + this.outgoing = new ArrayList(); + this.allEdges = new ArrayList(); + } + + /** + * Getter method for vertex name + * @return Vertex name + */ + public String getName() { + return this.name; + } + + /** + * Add an incoming edge to this vertex + * @param edge Incoming edge + */ + public void addIncoming(Edge edge) { + incoming.add(edge); + allEdges.add(edge); + } + + /** + * Add an outgoing edge to this vertex + * @param edge Outgoing edge + */ + public void addOutgoing(Edge edge) { + outgoing.add(edge); + allEdges.add(edge); + } + + /** + * Get a List of all outgoing edges of this vertex + * @return + */ + public ArrayList getOutgoing() { + return outgoing; + } + + public ArrayList getResidual() { + return allEdges; + } + + public int getTotalFlow() { + int res = 0; + for(Edge edge : outgoing) + res += edge.flow(); + for(Edge edge : incoming) + res -= edge.flow(); + return res; + } + + /** + * Get a dot code representation of this vertex + * @return Dot code representing the vertex + */ + public String toDotCode() { + return this.name + "[label=\"" + this.name + "\"];"; + } +}