@@ -55,6 +55,8 @@ public class NetworkFlowAnalyzer {
5555 private String sink ;
5656 private double maxFlowValue ;
5757 private boolean computed ;
58+ // Cached flow path decomposition (expensive to compute, requested by multiple methods)
59+ private List <FlowPath > cachedFlowPaths ;
5860
5961 /**
6062 * Creates a new NetworkFlowAnalyzer for the given graph.
@@ -96,12 +98,12 @@ public double compute(String source, String sink) {
9698 buildResidualGraph ();
9799
98100 maxFlowValue = 0 ;
101+ cachedFlowPaths = null ;
99102
100103 // Edmonds–Karp: BFS for shortest augmenting path
101104 while (true ) {
102105 Map <String , String > parent = new LinkedHashMap <String , String >();
103- Map <String , String > parentArcKey = new LinkedHashMap <String , String >();
104- double pathFlow = bfsAugmentingPath (parent , parentArcKey );
106+ double pathFlow = bfsAugmentingPath (parent );
105107
106108 if (pathFlow <= 0 ) break ;
107109
@@ -181,9 +183,9 @@ public Map<String, Double> getEdgeFlows() {
181183
182184 // Net flow direction
183185 if (fwdFlow > 1e-9 ) {
184- result .put (formatKey ( fwd ) , fwdFlow );
186+ result .put (fwd , fwdFlow );
185187 } else if (revFlow > 1e-9 ) {
186- result .put (formatKey ( rev ) , revFlow );
188+ result .put (rev , revFlow );
187189 }
188190 }
189191 return Collections .unmodifiableMap (result );
@@ -338,12 +340,15 @@ public int getAugmentingPathCount() {
338340
339341 /**
340342 * Decomposes the max flow into individual source-to-sink paths,
341- * each with its flow value.
343+ * each with its flow value. Results are cached to avoid redundant
344+ * O(V·E) recomputation across {@link #getSummary()},
345+ * {@link #getResult()}, and {@link #getAugmentingPathCount()}.
342346 *
343347 * @return list of flow paths, each described by vertices and flow value
344348 */
345349 public List <FlowPath > decomposeFlowPaths () {
346350 ensureComputed ();
351+ if (cachedFlowPaths != null ) return cachedFlowPaths ;
347352
348353 // Work on a copy of flows
349354 Map <String , Double > flowCopy = new HashMap <String , Double >(flow );
@@ -402,7 +407,8 @@ public List<FlowPath> decomposeFlowPaths() {
402407 Collections .unmodifiableList (pathVertices ), pathFlow ));
403408 }
404409
405- return Collections .unmodifiableList (paths );
410+ cachedFlowPaths = Collections .unmodifiableList (paths );
411+ return cachedFlowPaths ;
406412 }
407413
408414 // ── Result object ──────────────────────────────────────────────
@@ -597,8 +603,7 @@ private void buildResidualGraph() {
597603 }
598604 }
599605
600- private double bfsAugmentingPath (Map <String , String > parent ,
601- Map <String , String > parentArcKey ) {
606+ private double bfsAugmentingPath (Map <String , String > parent ) {
602607 Queue <String > queue = new ArrayDeque <String >();
603608 queue .add (source );
604609 parent .put (source , null );
@@ -613,7 +618,6 @@ private double bfsAugmentingPath(Map<String, String> parent,
613618 if (!parent .containsKey (v ) &&
614619 residualCapacity .getOrDefault (key , 0.0 ) > 1e-9 ) {
615620 parent .put (v , u );
616- parentArcKey .put (v , key );
617621 if (v .equals (sink )) {
618622 // Find bottleneck
619623 double pathFlow = Double .MAX_VALUE ;
@@ -643,17 +647,6 @@ private String directedKey(String from, String to) {
643647 return from + "->" + to ;
644648 }
645649
646- /**
647- * Formats a directed key as a human-readable string for display purposes.
648- * Since keys are already in "from->to" format, this is an identity operation.
649- *
650- * @param key the directed key string
651- * @return the key itself
652- */
653- private String formatKey (String key ) {
654- return key ;
655- }
656-
657650 private void validateVertex (String vertex , String name ) {
658651 if (vertex == null ) {
659652 throw new IllegalArgumentException (name + " must not be null" );
0 commit comments