Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 62 additions & 36 deletions Gvisual/src/gvisual/RandomWalkAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,63 +67,89 @@ public <V, E> double hittingTime(Graph<V, E> graph, V source, V target) {
* every unvisited vertex along the way. For a graph with V vertices
* and 10,000 simulations, this reduces total walks from V &times; 10,000
* to just 10,000 &mdash; a V&times; speedup.</p>
*
* <p><b>Array-indexed tracking:</b> Uses integer-indexed arrays instead
* of {@code HashSet<V>} for visited tracking and {@code Map<V, Long>}
* for accumulators. This eliminates per-simulation HashSet allocation
* (previously O(V) per sim × 10,000 sims = significant GC pressure),
* avoids autoboxing overhead, and provides cache-friendly sequential
* access. The visited array is reset via a generation counter rather
* than {@code Arrays.fill}, turning the O(V) per-sim reset into O(1).</p>
*/
public <V, E> Map<V, Double> hittingTimesFrom(Graph<V, E> graph, V source) {
validateGraph(graph);
validateNode(graph, source, "source");

// Initialize accumulators
Map<V, Long> totalSteps = new LinkedHashMap<>();
Map<V, Integer> reachedCount = new LinkedHashMap<>();
for (V v : graph.getVertices()) {
totalSteps.put(v, 0L);
reachedCount.put(v, 0);
int n = graph.getVertexCount();
List<V> vertexList = new ArrayList<>(graph.getVertices());
Map<V, Integer> vertexIndex = new HashMap<>(n * 2);
for (int i = 0; i < n; i++) {
vertexIndex.put(vertexList.get(i), i);
}

// Self-hitting time is always 0
totalSteps.put(source, 0L);
reachedCount.put(source, defaultSimulations);
int sourceIdx = vertexIndex.get(source);

int maxSteps = graph.getVertexCount() * graph.getVertexCount() * 10;
// Array-based accumulators (no boxing, no Map lookups in hot loop)
long[] totalSteps = new long[n];
int[] reachedCount = new int[n];
reachedCount[sourceIdx] = defaultSimulations;

int maxSteps = n * n * 10;

// Cache neighbor lists for fast random selection
Map<V, List<V>> neighborCache = new HashMap<>();
for (V v : graph.getVertices()) {
// Build adjacency as int[][] for cache-friendly, boxing-free traversal
int[][] adj = new int[n][];
for (int i = 0; i < n; i++) {
V v = vertexList.get(i);
Collection<V> nbrs = graph.getNeighbors(v);
neighborCache.put(v, nbrs != null ? new ArrayList<>(nbrs) : Collections.<V>emptyList());
if (nbrs == null || nbrs.isEmpty()) {
adj[i] = new int[0];
} else {
int[] neighbors = new int[nbrs.size()];
int j = 0;
for (V nb : nbrs) {
Integer idx = vertexIndex.get(nb);
if (idx != null) neighbors[j++] = idx;
}
adj[i] = (j == neighbors.length) ? neighbors : java.util.Arrays.copyOf(neighbors, j);
}
}

int targetCount = graph.getVertexCount() - 1; // exclude source
// Generation-based visited tracking: instead of allocating a new
// HashSet or calling Arrays.fill(visited, false) each simulation,
// we increment a generation counter. A vertex is "visited" when
// visitedGen[v] == currentGen. Reset is O(1) per simulation.
int[] visitedGen = new int[n];
int currentGen = 0;

for (int sim = 0; sim < defaultSimulations; sim++) {
// Track which vertices we haven't visited yet in this walk
Set<V> remaining = new HashSet<>(graph.getVertices());
remaining.remove(source);

V current = source;
for (int step = 1; step <= maxSteps && !remaining.isEmpty(); step++) {
List<V> nbrs = neighborCache.get(current);
if (nbrs == null || nbrs.isEmpty()) break;
current = nbrs.get(rng.nextInt(nbrs.size()));

if (remaining.remove(current)) {
// First visit to this vertex in this walk
totalSteps.put(current, totalSteps.get(current) + step);
reachedCount.put(current, reachedCount.get(current) + 1);
currentGen++;
visitedGen[sourceIdx] = currentGen;
int remaining = n - 1; // count of unvisited vertices

int currentIdx = sourceIdx;
for (int step = 1; step <= maxSteps && remaining > 0; step++) {
int[] nbrs = adj[currentIdx];
if (nbrs.length == 0) break;
currentIdx = nbrs[rng.nextInt(nbrs.length)];

if (visitedGen[currentIdx] != currentGen) {
visitedGen[currentIdx] = currentGen;
remaining--;
totalSteps[currentIdx] += step;
reachedCount[currentIdx]++;
}
}
}

// Compute averages
// Build result map
Map<V, Double> result = new LinkedHashMap<>();
for (V v : graph.getVertices()) {
if (v.equals(source)) {
result.put(v, 0.0);
for (int i = 0; i < n; i++) {
if (i == sourceIdx) {
result.put(vertexList.get(i), 0.0);
} else {
int reached = reachedCount.get(v);
result.put(v, reached == 0
result.put(vertexList.get(i), reachedCount[i] == 0
? Double.POSITIVE_INFINITY
: (double) totalSteps.get(v) / reached);
: (double) totalSteps[i] / reachedCount[i]);
}
}
return result;
Expand Down
Loading