Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

graph/path: Improve performance of YenKShortestPaths #2004

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

BarrensZeppelin
Copy link

@BarrensZeppelin BarrensZeppelin commented Nov 28, 2024

YenKShortestPaths's running time is dominated by calls to Dijkstra's shortest path algorithm, so it makes sense to focus on improvements there.
For Yen's algorithm, we are not interested in shortest path trees, but only a shortest path between some source and target node. We can exploit this in Dijkstra's algorithm by terminating when the target is discovered.
Of course this does not improve the worst-case complexity, but it should be a worthwhile optimization for many practical uses.

This variant can terminate early when the target is reached. This can
make a large difference in running time for Yen's k-shortest paths
algorithm, as it makes many calls to the shortest path subroutine.
Copy link
Member

@kortschak kortschak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please split this into two PRs.

@BarrensZeppelin
Copy link
Author

Done. See #2005

Copy link
Member

@kortschak kortschak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this can be done with composition; if g has From and Edge methods that return nil when u is the target, this has the same effect. I do not think we need additional API.

@BarrensZeppelin
Copy link
Author

BarrensZeppelin commented Nov 30, 2024

It does not have the same effect.
Consider the following graph, where we're looking for a path from node 1 to node 2.

gonum

With the implementation in the PR, only nodes 1, 2 and 4 are pushed to the priority queue, and only nodes 1 and 2 are popped.
With your suggestion (as I understand it), we would remove the edge from 2 to 3, but we would still compute the shortest path to nodes 4 and 5, which is wasted work.

I guess it's possible to achieve the same effect with composition by making the From function stateful: once the target is reached, remove all edges from the graph, but then correctness will depend on the exactly how Dijkstra's algorithm calls From, which seems brittle.

@kortschak

This comment was marked as outdated.

@kortschak
Copy link
Member

No, you're right.

// has a u-reachable negative edge weight that is discovered before reaching v.
//
// The time complexity of DijkstraFromTo is O(|E|.log|V|).
func DijkstraFromTo(u graph.Node, v graph.Node, g traverse.Graph) (path []graph.Node, weight float64) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func DijkstraFromTo(u graph.Node, v graph.Node, g traverse.Graph) (path []graph.Node, weight float64) {
func DijkstraFromTo(u, t graph.Node, g traverse.Graph) (path []graph.Node, weight float64) {

The second param is not a v.

Comment on lines 42 to 43
// Shared implementation of DijkstraFrom and DijkstraFromTo.
func dijkstraFrom(u, target graph.Node, g traverse.Graph) Shortest {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Shared implementation of DijkstraFrom and DijkstraFromTo.
func dijkstraFrom(u, target graph.Node, g traverse.Graph) Shortest {
func dijkstraFrom(u, t graph.Node, g traverse.Graph) Shortest {


np, weight := pt.To(test.NoPathFor.To().ID())
if pt.From().ID() == test.NoPathFor.From().ID() && (np != nil || !math.IsInf(weight, 1)) {
t.Errorf("%q %s: unexpected path:\ngot: path=%v weight=%f\nwant:path=<nil> weight=+Inf",
test.Name, tg.typ, np, weight)
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be in the loop, with helper sharing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

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.

2 participants