Skip to content

fix: replace O(N*K*N) BFS with multi-source Dijkstra in KMeansClustering#26

Open
ToB213 wants to merge 1 commit intoroborescue:masterfrom
ToB213:fix/kmeans-clustering-precompute
Open

fix: replace O(N*K*N) BFS with multi-source Dijkstra in KMeansClustering#26
ToB213 wants to merge 1 commit intoroborescue:masterfrom
ToB213:fix/kmeans-clustering-precompute

Conversation

@ToB213
Copy link
Copy Markdown

@ToB213 ToB213 commented Apr 22, 2026

Summary

Fixes #25.

This PR addresses two bugs in KMeansClustering.calcPathBased() that together made precompute unusable on standard competition maps.

Changes

Bug 1: Replace O(R·N·K·N) BFS with multi-source Dijkstra

The original implementation assigned each entity to its nearest center by calling comparePathDistance() for every candidate center. This ran an uncached BFS (O(N)) twice per comparison, resulting in O(R·N·K·N) total complexity.

This PR replaces that with assignByMultiSourceDijkstra(), which seeds all K centers into a priority queue simultaneously and expands them using edge-weighted Dijkstra. Each entity is assigned to the center with the minimum path distance in a single O(N log N) pass.

To support edge-weighted search, initShortestPath() (unweighted adjacency graph) is replaced by initWeightedGraph(). Edge weights are defined as:

cost(A -> B) = dist(A.center, midpoint of A-B edge)
             + dist(B.center, midpoint of B-A edge)

This matches the per-edge cost used in getPathDistance(), so the resulting cluster assignments are equivalent to the original intended path-distance Voronoi partition.

Bug 2: Guard against division by zero on empty clusters

When a cluster receives no entities, the division by clusterEntitiesList.get(index).size() throws ArithmeticException.

An isEmpty() guard is added before the division in both calcPathBased() and calcStandard().

Note: the multi-source Dijkstra fix also eliminates this problem structurally for connected graphs, since every reachable entity is guaranteed to be assigned. The isEmpty() guard remains as a defensive measure.

Cleanup

  • Removed initShortestPath(), shortestPathGraph, and the two getNearEntity() overloads that became dead code
  • Removed unused imports: LazyMap, HashSet, Set
  • shortestPath() (used by getNearAgent()) now reuses weightedGraph.keySet() as its adjacency list

Verified

Tested on the RSL 2025 kobe1 map (N=2,359, K=30, R=7) with the default agent configuration (30 PF / 30 FB / 30 AT).

Before After
precompute Some agents threw ArithmeticException immediately; remaining agents did not complete and were manually killed after 6 minutes BUILD SUCCESSFUL in 49s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

KMeansClustering.calcPathBased() causes precompute to time out and throws ArithmeticException

1 participant