@@ -116,13 +116,16 @@ public int hashCode() {
116116 /**
117117 * Computes all centrality metrics. Must be called before querying results.
118118 * Automatically skips recomputation if already computed.
119+ *
120+ * <p>Betweenness and closeness are computed in a single fused BFS pass
121+ * per source vertex, halving the O(V·E) traversal cost compared to
122+ * running two independent BFS sweeps.</p>
119123 */
120124 public void compute () {
121125 if (computed ) return ;
122126
123127 computeDegreeCentrality ();
124- computeBetweennessCentrality ();
125- computeClosenessCentrality ();
128+ computeBetweennessAndCloseness ();
126129 computed = true ;
127130 }
128131
@@ -306,6 +309,9 @@ public Map<String, Object> getSummary() {
306309 /**
307310 * Classifies the network topology based on degree distribution characteristics.
308311 *
312+ * <p>Reuses the degree data already computed by {@link #computeDegreeCentrality()}
313+ * instead of iterating all vertices again.</p>
314+ *
309315 * @return one of: "Trivial" (≤1 node), "Disconnected" (isolated nodes exist),
310316 * "Hub-and-Spoke" (one node dominates), "Distributed" (even degree distribution),
311317 * "Hierarchical" (moderate degree variance)
@@ -317,7 +323,7 @@ public String classifyTopology() {
317323 if (n <= 1 ) return "Trivial" ;
318324 if (graph .getEdgeCount () == 0 ) return "Disconnected" ;
319325
320- // Check for isolated nodes
326+ // Reuse cached degree data from computeDegreeCentrality()
321327 int isolated = 0 ;
322328 int maxDeg = 0 ;
323329 double sumDeg = 0 ;
@@ -333,11 +339,9 @@ public String classifyTopology() {
333339 double avgDeg = sumDeg / n ;
334340 if (avgDeg == 0 ) return "Disconnected" ;
335341
336- // Check hub-and-spoke: max degree much higher than average
337342 double hubRatio = maxDeg / avgDeg ;
338343 if (hubRatio > 4.0 && maxDeg > n * 0.3 ) return "Hub-and-Spoke" ;
339344
340- // Check for distributed (coefficient of variation of degree < 0.5)
341345 double sumSqDiff = 0 ;
342346 for (String node : graph .getVertices ()) {
343347 double diff = graph .degree (node ) - avgDeg ;
@@ -368,24 +372,29 @@ private void computeDegreeCentrality() {
368372 }
369373
370374 /**
371- * Betweenness centrality using Brandes' algorithm (2001).
372- * Complexity: O(V * E) for unweighted graphs.
375+ * Betweenness + closeness centrality in a single fused BFS pass per source.
376+ *
377+ * <p>Previously, betweenness used Brandes' algorithm (BFS + back-propagation
378+ * from each source) and closeness used a separate BFS from each source —
379+ * two independent O(V·E) traversals. This fused version does both in one
380+ * BFS per source vertex, halving the total graph traversal cost.</p>
373381 *
374- * <p>For each source vertex s, performs a BFS to compute shortest paths,
375- * then accumulates dependencies on the back-sweep. The result is normalized
376- * by 2/((V-1)(V-2)) for undirected graphs to give values in [0, 1] .</p>
382+ * <p>Betweenness: Brandes (2001), O(V·E) for unweighted graphs, normalized
383+ * by (V-1)(V-2) for undirected. Closeness: Wasserman-Faust normalization
384+ * for potentially disconnected graphs .</p>
377385 */
378- private void computeBetweennessCentrality () {
379- // Initialize all betweenness to 0
386+ private void computeBetweennessAndCloseness () {
387+ // Initialize all to 0
380388 for (String node : graph .getVertices ()) {
381389 betweennessCentrality .put (node , 0.0 );
390+ closenessCentrality .put (node , 0.0 );
382391 }
383392
384393 int n = graph .getVertexCount ();
385- if (n <= 2 ) return ;
394+ if (n <= 1 ) return ;
386395
387396 for (String s : graph .getVertices ()) {
388- // Stacks, predecessors, sigma, distance
397+ // --- Single BFS from s, shared by both metrics ---
389398 Deque <String > stack = new ArrayDeque <String >();
390399 Map <String , List <String >> predecessors = new HashMap <String , List <String >>();
391400 Map <String , Integer > sigma = new HashMap <String , Integer >();
@@ -400,10 +409,13 @@ private void computeBetweennessCentrality() {
400409 sigma .put (s , 1 );
401410 dist .put (s , 0 );
402411
403- // BFS from s
404412 Queue <String > queue = new LinkedList <String >();
405413 queue .add (s );
406414
415+ // Closeness accumulators for this source
416+ int sumDist = 0 ;
417+ int reachable = 0 ;
418+
407419 while (!queue .isEmpty ()) {
408420 String v = queue .poll ();
409421 stack .push (v );
@@ -414,8 +426,12 @@ private void computeBetweennessCentrality() {
414426
415427 // First visit to w
416428 if (dist .get (w ) < 0 ) {
429+ int nd = dist .get (v ) + 1 ;
430+ dist .put (w , nd );
417431 queue .add (w );
418- dist .put (w , dist .get (v ) + 1 );
432+ // Closeness: accumulate distance
433+ sumDist += nd ;
434+ reachable ++;
419435 }
420436
421437 // Shortest path to w via v?
@@ -426,26 +442,35 @@ private void computeBetweennessCentrality() {
426442 }
427443 }
428444
429- // Back-propagation of dependencies
430- Map < String , Double > delta = new HashMap < String , Double >();
431- for ( String t : graph . getVertices ()) {
432- delta .put (t , 0.0 );
445+ // --- Closeness for source s ---
446+ if ( reachable > 0 && sumDist > 0 ) {
447+ double cc = (( double ) reachable * reachable ) / (( n - 1.0 ) * sumDist );
448+ closenessCentrality .put (s , cc );
433449 }
434450
435- while (! stack . isEmpty ()) {
436- String w = stack . pop ();
437- for ( String v : predecessors . get ( w )) {
438- double contribution = (( double ) sigma . get ( v ) / sigma . get ( w )) * ( 1.0 + delta . get ( w ));
439- delta .put (v , delta . get ( v ) + contribution );
451+ // --- Betweenness back-propagation ---
452+ if ( n > 2 ) {
453+ Map < String , Double > delta = new HashMap < String , Double >();
454+ for ( String t : graph . getVertices ( )) {
455+ delta .put (t , 0.0 );
440456 }
441- if (!w .equals (s )) {
442- betweennessCentrality .put (w , betweennessCentrality .get (w ) + delta .get (w ));
457+
458+ while (!stack .isEmpty ()) {
459+ String w = stack .pop ();
460+ for (String v : predecessors .get (w )) {
461+ double contribution = ((double ) sigma .get (v ) / sigma .get (w ))
462+ * (1.0 + delta .get (w ));
463+ delta .put (v , delta .get (v ) + contribution );
464+ }
465+ if (!w .equals (s )) {
466+ betweennessCentrality .put (w ,
467+ betweennessCentrality .get (w ) + delta .get (w ));
468+ }
443469 }
444470 }
445471 }
446472
447- // Normalize for undirected graph: divide by 2 (each pair counted twice)
448- // and by (n-1)(n-2) to normalize to [0, 1]
473+ // Normalize betweenness for undirected graph: divide by (n-1)(n-2)
449474 double normFactor = (n - 1.0 ) * (n - 2.0 );
450475 if (normFactor > 0 ) {
451476 for (String node : graph .getVertices ()) {
@@ -455,58 +480,6 @@ private void computeBetweennessCentrality() {
455480 }
456481 }
457482
458- /**
459- * Closeness centrality: (reachable-1) / sum_of_distances.
460- * Uses BFS from each node to find shortest path distances.
461- *
462- * <p>For disconnected graphs, uses the Wasserman-Faust normalization:
463- * closeness = (reachable - 1)² / ((V - 1) * sumDist)
464- * which gives 0 for isolated nodes and properly scales for partial connectivity.</p>
465- */
466- private void computeClosenessCentrality () {
467- int n = graph .getVertexCount ();
468-
469- for (String s : graph .getVertices ()) {
470- if (n <= 1 ) {
471- closenessCentrality .put (s , 0.0 );
472- continue ;
473- }
474-
475- // BFS to compute distances from s
476- Map <String , Integer > dist = new HashMap <String , Integer >();
477- Queue <String > queue = new LinkedList <String >();
478- dist .put (s , 0 );
479- queue .add (s );
480-
481- int sumDist = 0 ;
482- int reachable = 0 ;
483-
484- while (!queue .isEmpty ()) {
485- String current = queue .poll ();
486- int currentDist = dist .get (current );
487-
488- for (edge e : graph .getIncidentEdges (current )) {
489- String neighbor = getOtherEnd (e , current );
490- if (neighbor != null && !dist .containsKey (neighbor )) {
491- int nd = currentDist + 1 ;
492- dist .put (neighbor , nd );
493- sumDist += nd ;
494- reachable ++;
495- queue .add (neighbor );
496- }
497- }
498- }
499-
500- if (reachable == 0 || sumDist == 0 ) {
501- closenessCentrality .put (s , 0.0 );
502- } else {
503- // Wasserman-Faust normalization for potentially disconnected graphs
504- double cc = ((double ) reachable * reachable ) / ((n - 1.0 ) * sumDist );
505- closenessCentrality .put (s , cc );
506- }
507- }
508- }
509-
510483 private String getOtherEnd (edge e , String current ) {
511484 String v1 = e .getVertex1 ();
512485 String v2 = e .getVertex2 ();
0 commit comments