day 25 optimize and improve heuristicsmain
commitc136885c6a89ce25f4012b33a756b40172659bf8
authorEric Blake <eblake@redhat.com>
Tue, 13 Feb 2024 21:35:37 +0000 (13 15:35 -0600)
committerEric Blake <eblake@redhat.com>
Tue, 13 Feb 2024 22:02:24 +0000 (13 16:02 -0600)
tree1637d7d54fbba94ef81d0543bbd2b736e28aca56
parentd356f73a775ce18778695ee70764edf393ff70bb
day 25 optimize and improve heuristics

My earlier algorithm assumes that the node with the most external
edges will always be on the other edge of the min-cut.  While it
worked for my input, it is fairly easy to construct a graph that
violates this property; several of the graphs at [1] went into an
inf-loop as a result.  Furthermore, it does O(n+e) work per iteration
determining the candidate node, and takes O(n) iterations (in reality,
closer to n/2 iterations); since the input is sparse enough that e is
approx 2n, this is roughly O(n^2) work, and trace shows 1135260 calls
to _round() and 843030 to visit().

The new algorithm takes a different approach: use two BFS to find two
distant nodes (a heuristic which happened to work for the sample, my
input, and all graphs at [1]); I suspect it is still possible to
construct a graph that violates my assumption, but I think it is
statistically less likely compared to my earlier heuristic.  Then,
with those nodes in hand, do 3 more BFS passes, removing the shortest
path between the two chosen nodes on each pass, in order to partition
the graph (assuming the two chosen nodes were indeed on opposite
sides) (not done here: I could short-circuit these BFS searches once
dst is found).  One more BFS search is then sufficient to count the
size on one side of the cut.  Since each BFS pass is O(n+e) work, and
I use only a constant number of passes, I have reduced the problem to
roughly O(n); trace shows a mere 59050 calls to v().  This speeds
things up to ~140ms.

[1] https://github.com/mattcl/unofficial-aoc2023-inputs/blob/master/day_025/solutions.md
2023/day25.m4
times.ods