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

Commit 6ae4c8f

Browse files
fix(KTrussAnalyzer): correct off-by-one in truss-number assignment
Per Cohen (2008), an edge's truss number is the maximum k such that the edge belongs to a k-truss. The peeling algorithm removes an edge at iteration k when its triangle support drops below k - 2, which means the edge survived the (k - 1)-truss but cannot be in the k-truss. Therefore truss(e) = k - 1, not k. The previous implementation set trussNumbers.put(e, k) at peel time, which over-reported truss numbers by one. It also pre-assigned k + 1 to currently-active edges ("tentative") which compounded the error when those edges were never peeled. Symptoms in KTrussAnalyzerTest: - testSingleEdge_noTriangle expected 2, got 3 (an edge with no triangles must have truss = 2; it is peeled at k = 3 because support 0 < 3 - 2) - testGetKTruss: 3-truss leaked the appendage edge C-D because its inflated truss number satisfied >= 3 Fix: - Record trussNumbers.put(e, k - 1) at peel time. - Tentatively assign trussNumbers.put(e, k) to active edges each round so still-surviving edges end with the highest k they made it through. Tests: KTrussAnalyzerTest 9/9 passing (2 previously-red cases now green). Full suite: 4604 run, 23 failures + 8 errors (down from 29F/8E baseline); all remaining failures are pre-existing and unrelated.
1 parent 3121220 commit 6ae4c8f

1 file changed

Lines changed: 18 additions & 12 deletions

File tree

Gvisual/src/gvisual/KTrussAnalyzer.java

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
import java.util.stream.Collectors;
77

88
/**
9-
* K-Truss Decomposition identifies cohesive subgraphs based on triangle
9+
* K-Truss Decomposition - identifies cohesive subgraphs based on triangle
1010
* support. Each Edge receives a <em>truss number</em> equal to the highest
1111
* k-truss it belongs to:
1212
*
1313
* <blockquote>
1414
* A <b>k-truss</b> is a maximal subgraph where every Edge participates in
15-
* at least (k 2) triangles within that subgraph.
15+
* at least (k - 2) triangles within that subgraph.
1616
* </blockquote>
1717
*
1818
* <p>K-truss sits between k-core (degree-based, too loose) and cliques
@@ -22,7 +22,7 @@
2222
* <h3>Algorithm (peeling, O(m · t_max))</h3>
2323
* <ol>
2424
* <li>Count triangle support for every Edge.</li>
25-
* <li>Starting from k = 2, iteratively remove edges with support &lt; k 2.</li>
25+
* <li>Starting from k = 2, iteratively remove edges with support &lt; k - 2.</li>
2626
* <li>When an Edge is removed, update triangle counts for affected edges.</li>
2727
* <li>An Edge's truss number is the k at which it was removed.</li>
2828
* </ol>
@@ -135,7 +135,7 @@ private void compute() {
135135
triangleSupport.put(e, count);
136136
}
137137

138-
// Step 2: Peeling iteratively remove edges with lowest support
138+
// Step 2: Peeling - iteratively remove edges with lowest support
139139
Set<Edge> active = new LinkedHashSet<>(remainingEdges);
140140
Map<Edge, Integer> support = new LinkedHashMap<>(triangleSupport);
141141

@@ -156,7 +156,11 @@ private void compute() {
156156

157157
for (Edge e : toRemove) {
158158
active.remove(e);
159-
trussNumbers.put(e, k);
159+
// Edge peeled at level k means it survived (k-1)-truss
160+
// but failed the support >= k-2 requirement for k-truss.
161+
// Per Cohen (2008), truss(e) = max k such that e is in k-truss,
162+
// which is k-1 at the moment of peeling.
163+
trussNumbers.put(e, k - 1);
160164
changed = true;
161165

162166
// Update support for edges sharing a triangle with e
@@ -176,7 +180,7 @@ private void compute() {
176180
}
177181
if (ou.equals(v) || ov.equals(v)) {
178182
if (shared != null && !shared.equals(v)) {
179-
// Both endpoints of e connect to other skip
183+
// Both endpoints of e connect to other - skip
180184
} else {
181185
shared = v;
182186
}
@@ -195,14 +199,16 @@ private void compute() {
195199
}
196200
}
197201

198-
// All remaining edges have support >= k-2, increase k
202+
// All remaining edges have support >= k-2; tentatively record
203+
// truss=k (consistent with peeling convention: actual value will
204+
// overwrite this if/when the edge is peeled at a later level).
199205
for (Edge e : active) {
200-
trussNumbers.put(e, k + 1); // tentative — will be overwritten if removed later
206+
trussNumbers.put(e, k);
201207
}
202208
k++;
203209
}
204210

205-
// Fix: edges still active at the end get truss number = k-1
211+
// Final maxTrussNumber = highest k for which any edge survived.
206212
maxTrussNumber = trussNumbers.values().stream()
207213
.mapToInt(Integer::intValue)
208214
.max()
@@ -233,7 +239,7 @@ public int getMaxTrussNumber() {
233239
}
234240

235241
/**
236-
* Extracts the k-truss subgraph all edges with truss number ≥ k
242+
* Extracts the k-truss subgraph - all edges with truss number ≥ k
237243
* and their incident vertices.
238244
*
239245
* @param k the truss parameter (k ≥ 2)
@@ -286,7 +292,7 @@ public int getTriangleSupport(Edge e) {
286292
}
287293

288294
/**
289-
* Returns the truss hierarchy nested structure showing how trusses
295+
* Returns the truss hierarchy - nested structure showing how trusses
290296
* decompose at each level.
291297
*
292298
* @return map from k to the set of edges in the k-truss but not in the (k+1)-truss
@@ -319,7 +325,7 @@ public List<String> compareTrussVsCore() {
319325
report.add(String.format("Graph degeneracy (max core): %d", kcore.getDegeneracy()));
320326
report.add("");
321327

322-
// For each vertex, compute its "truss participation" max truss of any incident Edge
328+
// For each vertex, compute its "truss participation" - max truss of any incident Edge
323329
Map<String, Integer> vertexTruss = new LinkedHashMap<>();
324330
for (String v : graph.getVertices()) {
325331
int maxT = 0;

0 commit comments

Comments
 (0)