@@ -374,108 +374,145 @@ private void computeDegreeCentrality() {
374374 /**
375375 * Betweenness + closeness centrality in a single fused BFS pass per source.
376376 *
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>
377+ * <p>Uses array-based storage indexed by vertex ordinal instead of
378+ * per-source HashMaps. This eliminates O(V) HashMap.put() calls per
379+ * source (V sources × V entries each = V² total), avoids Integer/Double
380+ * boxing, and provides better cache locality for the inner BFS loop.
381+ * Predecessor lists are still object-based but are allocated only when
382+ * an edge is discovered (lazy), not pre-allocated for every vertex.</p>
381383 *
382384 * <p>Betweenness: Brandes (2001), O(V·E) for unweighted graphs, normalized
383385 * by (V-1)(V-2) for undirected. Closeness: Wasserman-Faust normalization
384386 * for potentially disconnected graphs.</p>
385387 */
386388 private void computeBetweennessAndCloseness () {
387- // Initialize all to 0
389+ int n = graph .getVertexCount ();
390+
391+ // Initialize result maps
388392 for (String node : graph .getVertices ()) {
389393 betweennessCentrality .put (node , 0.0 );
390394 closenessCentrality .put (node , 0.0 );
391395 }
392-
393- int n = graph .getVertexCount ();
394396 if (n <= 1 ) return ;
395397
396- for (String s : graph .getVertices ()) {
397- // --- Single BFS from s, shared by both metrics ---
398- Deque <String > stack = new ArrayDeque <String >();
399- Map <String , List <String >> predecessors = new HashMap <String , List <String >>();
400- Map <String , Integer > sigma = new HashMap <String , Integer >();
401- Map <String , Integer > dist = new HashMap <String , Integer >();
402-
403- for (String t : graph .getVertices ()) {
404- predecessors .put (t , new ArrayList <String >());
405- sigma .put (t , 0 );
406- dist .put (t , -1 );
398+ // Build stable vertex-to-index mapping for array-based BFS
399+ List <String > vertexList = new ArrayList <String >(graph .getVertices ());
400+ Collections .sort (vertexList );
401+ Map <String , Integer > idxMap = new HashMap <String , Integer >(n * 2 );
402+ for (int i = 0 ; i < n ; i ++) {
403+ idxMap .put (vertexList .get (i ), i );
404+ }
405+
406+ // Pre-build adjacency as index arrays for cache-friendly traversal
407+ @ SuppressWarnings ("unchecked" )
408+ List <Integer >[] adj = new List [n ];
409+ for (int i = 0 ; i < n ; i ++) {
410+ adj [i ] = new ArrayList <Integer >();
411+ }
412+ for (edge e : graph .getEdges ()) {
413+ Integer ui = idxMap .get (e .getVertex1 ());
414+ Integer vi = idxMap .get (e .getVertex2 ());
415+ if (ui != null && vi != null && !ui .equals (vi )) {
416+ adj [ui ].add (vi );
417+ adj [vi ].add (ui );
407418 }
419+ }
408420
409- sigma .put (s , 1 );
410- dist .put (s , 0 );
421+ // Accumulator for betweenness (indexed, avoids per-source map lookups)
422+ double [] bcAccum = new double [n ];
423+
424+ // Reusable per-source arrays (allocated once, reset each iteration)
425+ int [] dist = new int [n ];
426+ int [] sigma = new int [n ];
427+ double [] delta = new double [n ];
428+ int [] bfsOrder = new int [n ]; // replaces Deque<String> stack
429+
430+ @ SuppressWarnings ("unchecked" )
431+ List <Integer >[] preds = new List [n ];
432+
433+ for (int s = 0 ; s < n ; s ++) {
434+ // Reset arrays for this source (Arrays.fill is memset-fast)
435+ Arrays .fill (dist , -1 );
436+ Arrays .fill (sigma , 0 );
437+ Arrays .fill (delta , 0.0 );
438+ for (int i = 0 ; i < n ; i ++) {
439+ preds [i ] = null ; // lazy allocation
440+ }
411441
412- Queue <String > queue = new LinkedList <String >();
413- queue .add (s );
442+ dist [s ] = 0 ;
443+ sigma [s ] = 1 ;
444+ int bfsHead = 0 , bfsTail = 0 ;
445+ bfsOrder [bfsTail ++] = s ;
414446
415- // Closeness accumulators for this source
447+ // Closeness accumulators
416448 int sumDist = 0 ;
417449 int reachable = 0 ;
418450
419- while (!queue .isEmpty ()) {
420- String v = queue .poll ();
421- stack .push (v );
422-
423- for (edge e : graph .getIncidentEdges (v )) {
424- String w = getOtherEnd (e , v );
425- if (w == null ) continue ;
426-
427- // First visit to w
428- if (dist .get (w ) < 0 ) {
429- int nd = dist .get (v ) + 1 ;
430- dist .put (w , nd );
431- queue .add (w );
432- // Closeness: accumulate distance
433- sumDist += nd ;
451+ // BFS using array-based queue (bfsOrder doubles as stack in reverse)
452+ int qHead = 0 ;
453+ int [] bfsQueue = bfsOrder ; // reuse same array
454+ // Actually we need a separate queue since bfsOrder is our stack
455+ // But we can use bfsOrder as both: BFS fills left-to-right,
456+ // back-propagation reads right-to-left (same as stack pop order)
457+ int orderIdx = 0 ;
458+
459+ // Simple array-based BFS queue
460+ int [] queue = new int [n ];
461+ int qStart = 0 , qEnd = 0 ;
462+ queue [qEnd ++] = s ;
463+
464+ while (qStart < qEnd ) {
465+ int v = queue [qStart ++];
466+ bfsOrder [orderIdx ++] = v ;
467+
468+ for (int w : adj [v ]) {
469+ if (dist [w ] < 0 ) {
470+ dist [w ] = dist [v ] + 1 ;
471+ queue [qEnd ++] = w ;
472+ sumDist += dist [w ];
434473 reachable ++;
435474 }
436-
437- // Shortest path to w via v?
438- if (dist .get (w ) == dist .get (v ) + 1 ) {
439- sigma .put (w , sigma .get (w ) + sigma .get (v ));
440- predecessors .get (w ).add (v );
475+ if (dist [w ] == dist [v ] + 1 ) {
476+ sigma [w ] += sigma [v ];
477+ if (preds [w ] == null ) {
478+ preds [w ] = new ArrayList <Integer >(4 );
479+ }
480+ preds [w ].add (v );
441481 }
442482 }
443483 }
444484
445- // --- Closeness for source s ---
485+ // Closeness for source s
446486 if (reachable > 0 && sumDist > 0 ) {
447487 double cc = ((double ) reachable * reachable ) / ((n - 1.0 ) * sumDist );
448- closenessCentrality .put (s , cc );
488+ closenessCentrality .put (vertexList . get ( s ) , cc );
449489 }
450490
451- // --- Betweenness back-propagation ---
491+ // Betweenness back-propagation (traverse BFS order in reverse)
452492 if (n > 2 ) {
453- Map <String , Double > delta = new HashMap <String , Double >();
454- for (String t : graph .getVertices ()) {
455- delta .put (t , 0.0 );
456- }
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 ));
493+ for (int idx = orderIdx - 1 ; idx >= 1 ; idx --) {
494+ int w = bfsOrder [idx ];
495+ if (preds [w ] != null ) {
496+ for (int v : preds [w ]) {
497+ double contribution = ((double ) sigma [v ] / sigma [w ])
498+ * (1.0 + delta [w ]);
499+ delta [v ] += contribution ;
500+ }
468501 }
502+ bcAccum [w ] += delta [w ];
469503 }
470504 }
471505 }
472506
473507 // Normalize betweenness for undirected graph: divide by (n-1)(n-2)
474508 double normFactor = (n - 1.0 ) * (n - 2.0 );
475509 if (normFactor > 0 ) {
476- for (String node : graph .getVertices ()) {
477- double raw = betweennessCentrality .get (node );
478- betweennessCentrality .put (node , raw / normFactor );
510+ for (int i = 0 ; i < n ; i ++) {
511+ betweennessCentrality .put (vertexList .get (i ), bcAccum [i ] / normFactor );
512+ }
513+ } else {
514+ for (int i = 0 ; i < n ; i ++) {
515+ betweennessCentrality .put (vertexList .get (i ), bcAccum [i ]);
479516 }
480517 }
481518 }
0 commit comments