Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
105 lines
3.6 KiB
Java
105 lines
3.6 KiB
Java
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<AbstractTestNode> pathWithBackEdge(int v) {
|
|
List<AbstractTestNode> 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<AbstractTestNode> linearPath(int v) {
|
|
List<AbstractTestNode> 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<AbstractTestNode> independentCycles(int k, int cycleLen) {
|
|
List<AbstractTestNode> all = new ArrayList<>();
|
|
for (int c = 0; c < k; c++) {
|
|
List<AbstractTestNode> 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<AbstractTestNode> 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);
|
|
}
|
|
}
|