package support; import java.util.ArrayList; import java.util.List; /** * Factory methods for constructing test graph topologies. * * Each method returns a list of nodes ready to pass to GraphUtils.tarjan(). * Topologies are chosen to stress the O(V²) defect in different ways. */ public class GraphFactory { /** * Path graph with a single back edge forming one large SCC: * N0 → N1 → N2 → … → N(v-1) → N0 * * Worst case for stack.contains(): when the back edge N(v-1)→N0 is * followed, the stack holds all V nodes. contains() must scan the * entire stack before finding N0 at the bottom. * * Total comparisons (defective): V*(V+1)/2 * Total comparisons (fixed): V (1 per edge) */ public static List pathWithBackEdge(int v) { List nodes = new ArrayList<>(); for (int i = 0; i < v; i++) { nodes.add(new AbstractTestNode("N" + i)); } // forward edges for (int i = 0; i < v - 1; i++) { nodes.get(i).addDep(nodes.get(i + 1)); } // single back edge nodes.get(v - 1).addDep(nodes.get(0)); return nodes; } /** * Linear path (no cycles — no back edges): * N0 → N1 → N2 → … → N(v-1) * * Every node is its own SCC. The stack at each step holds only nodes * whose SCC has not yet been closed. stack.contains() still fires for * each edge, scanning an average of V/2 entries. * * Total comparisons (defective): O(V²) in the worst path pattern * Total comparisons (fixed): O(E) = O(V) */ public static List linearPath(int v) { List nodes = new ArrayList<>(); for (int i = 0; i < v; i++) { nodes.add(new AbstractTestNode("N" + i)); } for (int i = 0; i < v - 1; i++) { nodes.get(i).addDep(nodes.get(i + 1)); } return nodes; } /** * k independent cycles, each of length cycleLen: * [N0→N1→…→N(len-1)→N0], [N(len)→…], … * * Produces k SCCs of size cycleLen. Good for verifying correctness * of SCC counting and node grouping. */ public static List independentCycles(int k, int cycleLen) { List all = new ArrayList<>(); for (int c = 0; c < k; c++) { List cycle = new ArrayList<>(); for (int i = 0; i < cycleLen; i++) { cycle.add(new AbstractTestNode("C" + c + "_N" + i)); } for (int i = 0; i < cycleLen - 1; i++) { cycle.get(i).addDep(cycle.get(i + 1)); } cycle.get(cycleLen - 1).addDep(cycle.get(0)); all.addAll(cycle); } return all; } /** * Diamond graph with a cross edge: * N0 → N1, N0 → N2, N1 → N3, N2 → N3 * Plus an optional back edge N3 → N0. * * Used to verify that SCC detection handles multiple paths correctly. */ public static List diamond(boolean withBackEdge) { AbstractTestNode n0 = new AbstractTestNode("N0"); AbstractTestNode n1 = new AbstractTestNode("N1"); AbstractTestNode n2 = new AbstractTestNode("N2"); AbstractTestNode n3 = new AbstractTestNode("N3"); n0.addDep(n1); n0.addDep(n2); n1.addDep(n3); n2.addDep(n3); if (withBackEdge) { n3.addDep(n0); } return List.of(n0, n1, n2, n3); } }