package unit; import java.util.*; /** * NimCyclicTreeAlgorithm — CWE-407 test for nim-0002 * * Models trees.cyclicTreeAux(n, visited): * slow: for v in visited: if v == n — O(N) scan per node, O(N²) for path tree * fast: HashSet[pointer] visited — O(1) per node, O(N) total * * Test: on a path graph (depth=N), slow does O(N²) scans; fast does O(N) ops. */ public class NimCyclicTreeAlgorithm { // Simple tree node (PNode proxy) static class PNode { final int id; final List sons; PNode(int id) { this.id = id; this.sons = new ArrayList<>(); } } // --- SLOW: O(N²) --- // for v in visited: if v == n: return true // visited is a path-stack (ArrayList acting as seq with add/remove) static class SlowCyclicCheck { long scanOps = 0; boolean cyclicTreeAux(PNode n, List visited) { if (n == null) return false; // Linear scan of visited (the path stack) for (PNode v : visited) { scanOps++; if (v == n) return true; } visited.add(n); for (PNode son : n.sons) { if (cyclicTreeAux(son, visited)) return true; } visited.remove(visited.size() - 1); return false; } boolean cyclicTree(PNode n) { List visited = new ArrayList<>(); return cyclicTreeAux(n, visited); } } // --- FAST: O(N) --- // visited is a HashSet[pointer] (IdentityHashMap backed set) static class FastCyclicCheck { long hashOps = 0; // Using IdentityHashMap to simulate pointer-based HashSet boolean cyclicTreeAux(PNode n, Set visited) { if (n == null) return false; hashOps++; if (visited.contains(n)) return true; // O(1) identity hash visited.add(n); for (PNode son : n.sons) { if (cyclicTreeAux(son, visited)) return true; } visited.remove(n); return false; } boolean cyclicTree(PNode n) { // IdentityHashMap simulates pointer-equality HashSet (Nim's HashSet[pointer]) Set visited = Collections.newSetFromMap(new IdentityHashMap<>()); return cyclicTreeAux(n, visited); } } // Build an acyclic path graph: 0 -> 1 -> 2 -> ... -> n-1 // This is worst case for the slow algorithm: depth = n, visited grows to n static PNode buildPathGraph(int n) { PNode[] nodes = new PNode[n]; for (int i = 0; i < n; i++) nodes[i] = new PNode(i); for (int i = 0; i < n - 1; i++) nodes[i].sons.add(nodes[i + 1]); return nodes[0]; } // Build a balanced binary tree of depth d (no cycles) static PNode buildBinaryTree(int depth) { if (depth == 0) return new PNode(0); PNode root = new PNode(depth); root.sons.add(buildBinaryTree(depth - 1)); root.sons.add(buildBinaryTree(depth - 1)); return root; } public static void main(String[] args) { System.out.println("NimCyclicTreeAlgorithm — nim-0002"); System.out.println(" Pattern: for v in visited: if v == n — O(N²) vs HashSet — O(N)"); System.out.println(); int passed = 0; int total = 0; // Test 1: Path graphs (worst case for O(N²)) int[] pathSizes = {100, 300, 500, 800}; for (int n : pathSizes) { PNode root = buildPathGraph(n); SlowCyclicCheck slow = new SlowCyclicCheck(); FastCyclicCheck fast = new FastCyclicCheck(); boolean slowResult = slow.cyclicTree(root); boolean fastResult = fast.cyclicTree(root); long slowOps = slow.scanOps; long fastOps = fast.hashOps; // No cycles — both should return false boolean correctResult = !slowResult && !fastResult; // Slow: at depth d, visited has d nodes. Sum for path of N nodes: 0+1+2+...+(N-1) = N(N-1)/2 boolean slowIsQuadratic = slowOps >= (long) n * (n - 1) / 4; // conservative boolean fastIsLinear = fastOps <= n + 1; // at most N+1 hash checks total += 3; if (correctResult) { System.out.println("PASS path N=" + n + ": no cycle detected (correct)"); passed++; } else { System.out.println("FAIL path N=" + n + ": wrong cycle detection slow=" + slowResult + " fast=" + fastResult); } if (slowIsQuadratic) { System.out.println("PASS path N=" + n + ": slow O(N²) scans=" + slowOps + " >= N(N-1)/4=" + (n * (n - 1) / 4)); passed++; } else { System.out.println("FAIL path N=" + n + ": slow not quadratic, scans=" + slowOps); } if (fastIsLinear) { System.out.println("PASS path N=" + n + ": fast O(N) ops=" + fastOps + " <= N+1=" + (n + 1)); passed++; } else { System.out.println("FAIL path N=" + n + ": fast not linear, ops=" + fastOps); } } // Test 2: Cycle detection correctness — introduce a back edge { PNode[] nodes = new PNode[5]; for (int i = 0; i < 5; i++) nodes[i] = new PNode(i); nodes[0].sons.add(nodes[1]); nodes[1].sons.add(nodes[2]); nodes[2].sons.add(nodes[0]); // cycle: 2 -> 0 SlowCyclicCheck slow = new SlowCyclicCheck(); FastCyclicCheck fast = new FastCyclicCheck(); boolean slowDetects = slow.cyclicTree(nodes[0]); boolean fastDetects = fast.cyclicTree(nodes[0]); total += 2; if (slowDetects) { System.out.println("PASS cycle: slow correctly detects cycle"); passed++; } else { System.out.println("FAIL cycle: slow missed cycle"); } if (fastDetects) { System.out.println("PASS cycle: fast correctly detects cycle"); passed++; } else { System.out.println("FAIL cycle: fast missed cycle"); } } System.out.println(); System.out.println(passed + "/" + total + " PASS"); if (passed != total) { throw new AssertionError(passed + "/" + total + " tests passed"); } } }