Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit 351f39f

Browse files
refactor: extract duplicate BFS in NetworkFlowAnalyzer, reduce redundant computation (#79)
The getMinCut() and getSourceSide() methods both contained identical BFS traversals to find vertices reachable from source in the residual graph. Extracted into a single findReachableFromSource() private method. Also fixed getSummary() and getResult() which were calling decomposeFlowPaths(), getMinCut(), and getBottleneckEdges() independently — each triggering their own traversals. Now each method computes these expensive results once and reuses them. Changes: - New private findReachableFromSource() method (single BFS) - getMinCut() delegates to findReachableFromSource() - getSourceSide() delegates to findReachableFromSource() - getSummary() computes paths/minCut/bottlenecks once, reuses - getResult() computes paths/minCut/bottlenecks once, reuses No behavioral changes — all 52 existing tests remain valid.
1 parent ce5080f commit 351f39f

1 file changed

Lines changed: 35 additions & 34 deletions

File tree

Gvisual/src/gvisual/NetworkFlowAnalyzer.java

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -203,16 +203,16 @@ public double getFlowOnEdge(String v1, String v2) {
203203
}
204204

205205
/**
206-
* Returns edges that form the minimum cut (max-flow min-cut theorem).
207-
* These are edges crossing from the source side to the sink side in
208-
* the residual graph where no augmenting path exists.
206+
* BFS from source through residual edges with positive capacity.
207+
* This is the "source side" of the min cut — the set of vertices
208+
* still reachable from source after max flow is saturated.
209209
*
210-
* @return list of edges in the minimum cut
210+
* <p>Extracted to avoid duplicating the same BFS in
211+
* {@link #getMinCut()} and {@link #getSourceSide()}.</p>
212+
*
213+
* @return set of vertices reachable from source in the residual graph
211214
*/
212-
public List<edge> getMinCut() {
213-
ensureComputed();
214-
215-
// BFS from source in residual graph to find reachable vertices
215+
private Set<String> findReachableFromSource() {
216216
Set<String> reachable = new HashSet<String>();
217217
Queue<String> queue = new LinkedList<String>();
218218
queue.add(source);
@@ -223,14 +223,27 @@ public List<edge> getMinCut() {
223223
Set<String> neighbors = residualAdj.get(u);
224224
if (neighbors == null) continue;
225225
for (String v : neighbors) {
226-
List<String> key = directedKey(u, v);
227226
if (!reachable.contains(v) &&
228-
residualCapacity.getOrDefault(key, 0.0) > 1e-9) {
227+
residualCapacity.getOrDefault(directedKey(u, v), 0.0) > 1e-9) {
229228
reachable.add(v);
230229
queue.add(v);
231230
}
232231
}
233232
}
233+
return reachable;
234+
}
235+
236+
/**
237+
* Returns edges that form the minimum cut (max-flow min-cut theorem).
238+
* These are edges crossing from the source side to the sink side in
239+
* the residual graph where no augmenting path exists.
240+
*
241+
* @return list of edges in the minimum cut
242+
*/
243+
public List<edge> getMinCut() {
244+
ensureComputed();
245+
246+
Set<String> reachable = findReachableFromSource();
234247

235248
// Min cut edges: original edges with one end in reachable, other not
236249
List<edge> cut = new ArrayList<edge>();
@@ -253,24 +266,7 @@ public List<edge> getMinCut() {
253266
*/
254267
public Set<String> getSourceSide() {
255268
ensureComputed();
256-
Set<String> reachable = new HashSet<String>();
257-
Queue<String> queue = new LinkedList<String>();
258-
queue.add(source);
259-
reachable.add(source);
260-
261-
while (!queue.isEmpty()) {
262-
String u = queue.poll();
263-
Set<String> neighbors = residualAdj.get(u);
264-
if (neighbors == null) continue;
265-
for (String v : neighbors) {
266-
if (!reachable.contains(v) &&
267-
residualCapacity.getOrDefault(directedKey(u, v), 0.0) > 1e-9) {
268-
reachable.add(v);
269-
queue.add(v);
270-
}
271-
}
272-
}
273-
return Collections.unmodifiableSet(reachable);
269+
return Collections.unmodifiableSet(findReachableFromSource());
274270
}
275271

276272
/**
@@ -490,10 +486,12 @@ public FlowResult(String source, String sink, double maxFlow,
490486
public FlowResult getResult() {
491487
ensureComputed();
492488
List<FlowPath> paths = decomposeFlowPaths();
489+
List<edge> minCut = getMinCut();
490+
List<edge> bottlenecks = getBottleneckEdges();
493491
return new FlowResult(
494492
source, sink, maxFlowValue,
495493
getTotalCapacity(), getUtilisation(),
496-
getMinCut().size(), getBottleneckEdges().size(),
494+
minCut.size(), bottlenecks.size(),
497495
paths.size(), getEdgeFlows(), paths
498496
);
499497
}
@@ -507,6 +505,12 @@ public FlowResult getResult() {
507505
*/
508506
public String getSummary() {
509507
ensureComputed();
508+
509+
// Compute expensive results once
510+
List<FlowPath> paths = decomposeFlowPaths();
511+
List<edge> minCut = getMinCut();
512+
List<edge> bottlenecks = getBottleneckEdges();
513+
510514
StringBuilder sb = new StringBuilder();
511515
sb.append("=== Network Flow Analysis ===\n");
512516
sb.append(String.format("Vertices: %d | Edges: %d\n",
@@ -515,11 +519,8 @@ public String getSummary() {
515519
sb.append(String.format("Maximum flow: %.2f\n", maxFlowValue));
516520
sb.append(String.format("Total capacity: %.2f\n", getTotalCapacity()));
517521
sb.append(String.format("Utilisation: %.1f%%\n", getUtilisation()));
518-
sb.append(String.format("Min-cut edges: %d\n", getMinCut().size()));
519-
sb.append(String.format("Bottleneck edges: %d\n",
520-
getBottleneckEdges().size()));
521-
522-
List<FlowPath> paths = decomposeFlowPaths();
522+
sb.append(String.format("Min-cut edges: %d\n", minCut.size()));
523+
sb.append(String.format("Bottleneck edges: %d\n", bottlenecks.size()));
523524
sb.append(String.format("Flow paths: %d\n", paths.size()));
524525

525526
if (!paths.isEmpty()) {

0 commit comments

Comments
 (0)