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

Commit 5277d14

Browse files
perf: O(1) adjacency and degree lookups in GraphColoringAnalyzer
Pre-build adjacency sets (HashMap<String, Set<String>>) and degree cache at construction time — O(V+E) once — then reuse throughout: - chromaticLowerBound(): greedy clique detection used graph.isNeighbor() which is O(degree) per call in JUNG's sparse graph backing store; inner loop was O(V × candidates × clique × deg). Now uses HashSet.contains() for O(1) per check. Also caches degree for candidate sorting (was O(V log V) graph.degree() calls). - analyzeColorClasses(): independent set verification used the same O(degree) isNeighbor() in O(color_class²) pairs. Now O(1) per pair. - getOrderedVertices(LARGEST_FIRST): sort comparator called graph.degree() per comparison — O(V log V) API calls. Now uses pre-cached degreeCache for O(1) lookups. - computeDSatur(): removed redundant per-call degree cache construction; reuses the instance-level cache. - maxDegree() / edgeChromaticBounds(): were O(V) scans each. Now O(1) via cachedMaxDegree computed at construction. - greedyColor(): used maxDegree() (formerly O(V)) for boolean[] sizing; now uses cachedMaxDegree directly.
1 parent cf3b31c commit 5277d14

1 file changed

Lines changed: 87 additions & 35 deletions

File tree

Gvisual/src/gvisual/GraphColoringAnalyzer.java

Lines changed: 87 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@ public enum VertexOrdering {
4545

4646
private final Graph<String, Edge> graph;
4747

48+
/**
49+
* Pre-built adjacency sets for O(1) neighbor lookups.
50+
* Replaces repeated {@code graph.isNeighbor()} calls (which are O(degree)
51+
* in JUNG's sparse graph implementations) with HashSet.contains() — O(1).
52+
* Built once at construction; reused by chromaticLowerBound(),
53+
* analyzeColorClasses(), and anywhere else that tests adjacency.
54+
*/
55+
private final Map<String, Set<String>> adjacency;
56+
57+
/**
58+
* Pre-cached degree per vertex. Eliminates repeated {@code graph.degree()}
59+
* calls in sort comparators (O(V log V) calls in LARGEST_FIRST),
60+
* edgeChromaticBounds(), maxDegree(), and DSatur initialization.
61+
*/
62+
private final Map<String, Integer> degreeCache;
63+
64+
/** Cached max degree — computed once from degreeCache. */
65+
private final int cachedMaxDegree;
66+
4867
/**
4968
* Creates a new GraphColoringAnalyzer for the given graph.
5069
*
@@ -56,6 +75,18 @@ public GraphColoringAnalyzer(Graph<String, Edge> graph) {
5675
throw new IllegalArgumentException("Graph must not be null");
5776
}
5877
this.graph = graph;
78+
79+
// Pre-build adjacency sets and degree cache — O(V + E) once,
80+
// then all subsequent neighbor/degree queries are O(1).
81+
this.adjacency = GraphUtils.buildAdjacencyMap(graph);
82+
this.degreeCache = new HashMap<>(graph.getVertexCount() * 2);
83+
int maxDeg = 0;
84+
for (Map.Entry<String, Set<String>> entry : adjacency.entrySet()) {
85+
int deg = entry.getValue().size();
86+
degreeCache.put(entry.getKey(), deg);
87+
if (deg > maxDeg) maxDeg = deg;
88+
}
89+
this.cachedMaxDegree = maxDeg;
5990
}
6091

6192
// ── Greedy Coloring ─────────────────────────────────────────────
@@ -156,23 +187,24 @@ public ColoringResult computeDSatur() {
156187
Map<String, Integer> colorAssignment = new HashMap<>();
157188
Map<String, Set<Integer>> saturation = new HashMap<>();
158189

159-
// Cache degree per vertex to avoid repeated graph.degree() calls
160-
Map<String, Integer> degreeCache = new HashMap<>();
190+
// Use the pre-computed degree cache from construction instead of
191+
// calling graph.degree() V times here.
161192
for (String v : vertices) {
162193
saturation.put(v, new HashSet<>());
163-
degreeCache.put(v, graph.degree(v));
164194
}
165195

166196
// Priority queue ordered by: saturation desc, degree desc, name asc.
167197
// Using a TreeSet with a Comparator gives O(log V) removal of the
168198
// max-priority element and O(log V) re-insertion on saturation updates,
169199
// replacing the previous O(V) linear scan per iteration.
200+
// Captures the instance-level degreeCache for O(1) degree lookups.
201+
final Map<String, Integer> localDegreeCache = this.degreeCache;
170202
TreeSet<String> pq = new TreeSet<>((a, b) -> {
171203
int satA = saturation.get(a).size();
172204
int satB = saturation.get(b).size();
173205
if (satA != satB) return Integer.compare(satB, satA); // desc
174-
int degA = degreeCache.get(a);
175-
int degB = degreeCache.get(b);
206+
int degA = localDegreeCache.getOrDefault(a, 0);
207+
int degB = localDegreeCache.getOrDefault(b, 0);
176208
if (degA != degB) return Integer.compare(degB, degA); // desc
177209
return a.compareTo(b); // asc (tiebreaker for TreeSet uniqueness)
178210
});
@@ -237,6 +269,20 @@ public ColoringResult computeDSatur() {
237269
*
238270
* @return lower bound on chromatic number (clique number estimate)
239271
*/
272+
/**
273+
* Computes a lower bound on the chromatic number using greedy clique
274+
* detection. The chromatic number is at least the size of the largest
275+
* clique found.
276+
*
277+
* <p><b>Performance:</b> Uses pre-built adjacency sets for O(1)
278+
* neighbor checks (via {@code HashSet.contains()}) instead of the
279+
* previous {@code graph.isNeighbor()} which is O(degree) per call
280+
* in JUNG's sparse graph backing store. Also uses the pre-cached
281+
* degree map for candidate sorting, eliminating O(V log V)
282+
* graph API calls per vertex.</p>
283+
*
284+
* @return lower bound on chromatic number (clique number estimate)
285+
*/
240286
public int chromaticLowerBound() {
241287
Collection<String> vertices = graph.getVertices();
242288
if (vertices.isEmpty()) {
@@ -250,16 +296,21 @@ public int chromaticLowerBound() {
250296
List<String> clique = new ArrayList<>();
251297
clique.add(start);
252298

299+
Set<String> startNeighbors = adjacency.get(start);
300+
if (startNeighbors == null || startNeighbors.isEmpty()) continue;
301+
253302
// Sort candidates by degree descending for better heuristic
254-
List<String> candidates = new ArrayList<>();
255-
Collection<String> neighbors = GraphUtils.neighborsOf(graph, start);
256-
candidates.addAll(neighbors);
257-
candidates.sort((a, b) -> Integer.compare(graph.degree(b), graph.degree(a)));
303+
List<String> candidates = new ArrayList<>(startNeighbors);
304+
candidates.sort((a, b) -> Integer.compare(
305+
degreeCache.getOrDefault(b, 0),
306+
degreeCache.getOrDefault(a, 0)));
258307

259308
for (String candidate : candidates) {
309+
// O(1) adjacency check per clique member via HashSet
310+
Set<String> candidateNeighbors = adjacency.get(candidate);
260311
boolean adjacent = true;
261312
for (String member : clique) {
262-
if (!graph.isNeighbor(candidate, member)) {
313+
if (!candidateNeighbors.contains(member)) {
263314
adjacent = false;
264315
break;
265316
}
@@ -456,12 +507,15 @@ public Map<String, Object> analyzeColorClasses(ColoringResult result) {
456507
analysis.put("balanceRatio", largest > 0
457508
? (double) smallest / largest : 1.0);
458509

459-
// Verify each color class is an independent set
510+
// Verify each color class is an independent set.
511+
// Uses pre-built adjacency sets for O(1) checks instead of
512+
// graph.isNeighbor() which is O(degree) per call.
460513
boolean allIndependent = true;
461514
for (List<String> cls : classes.values()) {
462515
for (int i = 0; i < cls.size(); i++) {
516+
Set<String> iNeighbors = adjacency.get(cls.get(i));
463517
for (int j = i + 1; j < cls.size(); j++) {
464-
if (graph.isNeighbor(cls.get(i), cls.get(j))) {
518+
if (iNeighbors != null && iNeighbors.contains(cls.get(j))) {
465519
allIndependent = false;
466520
break;
467521
}
@@ -492,21 +546,17 @@ public Map<String, Object> analyzeColorClasses(ColoringResult result) {
492546
*
493547
* @return array with [lower bound, upper bound] for Edge chromatic number
494548
*/
549+
/**
550+
* Estimates the Edge chromatic number bounds using Vizing's theorem.
551+
* Uses the pre-cached max degree — O(1) instead of O(V) graph API calls.
552+
*
553+
* @return array with [lower bound, upper bound] for Edge chromatic number
554+
*/
495555
public int[] edgeChromaticBounds() {
496-
Collection<String> vertices = graph.getVertices();
497-
if (vertices.isEmpty() || graph.getEdgeCount() == 0) {
556+
if (graph.getVertexCount() == 0 || graph.getEdgeCount() == 0) {
498557
return new int[]{0, 0};
499558
}
500-
501-
int maxDegree = 0;
502-
for (String v : vertices) {
503-
int deg = graph.degree(v);
504-
if (deg > maxDegree) {
505-
maxDegree = deg;
506-
}
507-
}
508-
509-
return new int[]{maxDegree, maxDegree + 1};
559+
return new int[]{cachedMaxDegree, cachedMaxDegree + 1};
510560
}
511561

512562
/**
@@ -515,15 +565,14 @@ public int[] edgeChromaticBounds() {
515565
*
516566
* @return maximum degree
517567
*/
568+
/**
569+
* Returns the maximum vertex degree (Δ), using the pre-cached value.
570+
* O(1) instead of O(V) graph API calls.
571+
*
572+
* @return maximum degree
573+
*/
518574
public int maxDegree() {
519-
int max = 0;
520-
for (String v : graph.getVertices()) {
521-
int deg = graph.degree(v);
522-
if (deg > max) {
523-
max = deg;
524-
}
525-
}
526-
return max;
575+
return cachedMaxDegree;
527576
}
528577

529578
// ── Report Generation ───────────────────────────────────────────
@@ -589,8 +638,12 @@ private List<String> getOrderedVertices(VertexOrdering ordering) {
589638
Collections.sort(vertices);
590639
break;
591640
case LARGEST_FIRST:
641+
// Use cached degree map for O(1) lookups in the comparator,
642+
// eliminating O(V log V) graph.degree() API calls.
592643
vertices.sort((a, b) -> {
593-
int cmp = Integer.compare(graph.degree(b), graph.degree(a));
644+
int cmp = Integer.compare(
645+
degreeCache.getOrDefault(b, 0),
646+
degreeCache.getOrDefault(a, 0));
594647
return cmp != 0 ? cmp : a.compareTo(b);
595648
});
596649
break;
@@ -691,8 +744,7 @@ private ColoringResult greedyColor(List<String> vertexOrder) {
691744
// Upper bound on colors needed is max-degree + 1; pre-allocate a
692745
// boolean array instead of a HashSet<Integer> per vertex to avoid
693746
// boxing and hashing overhead in the inner loop.
694-
int maxDeg = maxDegree();
695-
boolean[] usedColors = new boolean[maxDeg + 2];
747+
boolean[] usedColors = new boolean[cachedMaxDegree + 2];
696748

697749
for (String vertex : vertexOrder) {
698750
// Reset only the slots we dirtied (cheaper than Arrays.fill

0 commit comments

Comments
 (0)