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

Commit 37f7cb1

Browse files
perf: cache neighbor sets in CliqueAnalyzer Bron-Kerbosch
getNeighbors() was creating a new LinkedHashSet copy of each vertex's adjacency list on every call. During Bron-Kerbosch recursion and pivot selection, this is called O(V * recursion depth) times — for dense graphs with many cliques, the temporary allocation pressure dominates runtime. Fix: build a HashMap<String, Set<String>> neighbor cache once at the start of compute(), before entering recursion. The recursive bronKerbosch(), choosePivot(), and countIntersection() methods now read from this cache with O(1) lookups instead of allocating. Defensive fallback retained for vertices not in cache (should never trigger after compute() initialisation).
1 parent d753d2e commit 37f7cb1

1 file changed

Lines changed: 20 additions & 0 deletions

File tree

Gvisual/src/gvisual/CliqueAnalyzer.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
public class CliqueAnalyzer {
3232

3333
private final Graph<String, edge> graph;
34+
private Map<String, Set<String>> neighborCache;
3435
private List<Set<String>> cliques;
3536
private boolean computed;
3637

@@ -45,6 +46,7 @@ public CliqueAnalyzer(Graph<String, edge> graph) {
4546
throw new IllegalArgumentException("Graph must not be null");
4647
}
4748
this.graph = graph;
49+
this.neighborCache = new HashMap<String, Set<String>>();
4850
this.cliques = new ArrayList<Set<String>>();
4951
this.computed = false;
5052
}
@@ -68,6 +70,20 @@ public CliqueAnalyzer compute() {
6870
return this;
6971
}
7072

73+
// Pre-build neighbor sets once so the recursive hot-path never
74+
// allocates temporary collections. Without this cache each
75+
// getNeighbors() call created a new LinkedHashSet — O(V^2)
76+
// allocations in the worst case during pivot selection alone.
77+
neighborCache = new HashMap<String, Set<String>>(vertices.size() * 2);
78+
for (String v : vertices) {
79+
Collection<String> neighbors = graph.getNeighbors(v);
80+
if (neighbors == null) {
81+
neighborCache.put(v, new LinkedHashSet<String>());
82+
} else {
83+
neighborCache.put(v, new LinkedHashSet<String>(neighbors));
84+
}
85+
}
86+
7187
// For isolated vertices (no neighbors), each is a trivial maximal clique
7288
Set<String> P = new LinkedHashSet<String>(vertices);
7389
Set<String> R = new LinkedHashSet<String>();
@@ -166,6 +182,10 @@ private int countIntersection(Set<String> a, Set<String> b) {
166182
}
167183

168184
private Set<String> getNeighbors(String vertex) {
185+
Set<String> cached = neighborCache.get(vertex);
186+
if (cached != null) return cached;
187+
// Fallback for vertices not in the cache (should not happen
188+
// after compute() builds the cache, but defensive).
169189
Collection<String> neighbors = graph.getNeighbors(vertex);
170190
if (neighbors == null) return new LinkedHashSet<String>();
171191
return new LinkedHashSet<String>(neighbors);

0 commit comments

Comments
 (0)