| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| // @dart = 2.9 |
| |
| import 'dart:collection'; |
| |
| /// Dijkstra's algorithm for single source shortest path. |
| /// |
| /// Adopted from https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm#Pseudocode |
| /// |
| /// Note that this is not an optimal implementation in that it uses a |
| /// (Splay) Tree as the priority queue which takes O(log n) time to (fake) a |
| /// decrease of priority whereas e.g. a fibonacci heap would have done it in |
| /// (amortized) O(1). |
| class DijkstrasAlgorithm<E> { |
| Map<GraphNode<E>, int> dist = new Map<GraphNode<E>, int>(); |
| Map<GraphNode<E>, GraphNode<E>> prev = new Map<GraphNode<E>, GraphNode<E>>(); |
| |
| DijkstrasAlgorithm(Iterable<GraphNode<E>> graphNodes, GraphNode<E> source, |
| int Function(E, E) comparator, int Function(E, E) distance) { |
| SplayTreeSet<GraphNode<E>> q = new SplayTreeSet<GraphNode<E>>((a, b) { |
| int distA = dist[a]; |
| int distB = dist[b]; |
| |
| int when0() { |
| if (identical(a, b)) return 0; |
| int result = comparator(a.node, b.node); |
| if (result == 0) { |
| throw "The nodes ${b.node} and ${a.node} are not the same but " |
| "compares to the same. That's not allowed!"; |
| } |
| return result; |
| } |
| |
| if (distA != null && distB == null) return -1; |
| if (distA == null && distB != null) return 1; |
| if (distA == null && distB == null) { |
| return when0(); |
| } |
| if (distA < distB) return -1; |
| if (distA > distB) return 1; |
| return when0(); |
| }); |
| |
| dist[source] = 0; |
| int index = 0; |
| for (GraphNode<E> g in graphNodes) { |
| // dist and prev not set, we see "null" as "infinity" and "undefined". |
| if (!q.add(g)) { |
| throw "Couldn't add ${g.node} (index $index)."; |
| } |
| index++; |
| } |
| |
| while (q.isNotEmpty) { |
| GraphNode<E> u = q.first; |
| int distToU = dist[u]; |
| if (distToU == null) { |
| // No path to any of the remaining ${q.length} nodes. |
| break; |
| } |
| q.remove(u); |
| for (GraphNode<E> v in u.outgoing) { |
| // Wikipedia says "only v that are still in Q" but it shouldn't matter |
| // --- the length via u would be longer. |
| int distanceUToV = distance(u.node, v.node); |
| if (distanceUToV < 0) throw "Got negative distance. That's not allowed"; |
| int alt = distToU + distanceUToV; |
| int distToV = dist[v]; |
| if (distToV == null || alt < distToV) { |
| // Decrease length (decrease priority in priority queue). |
| q.remove(v); |
| dist[v] = alt; |
| prev[v] = u; |
| q.add(v); |
| } |
| } |
| } |
| } |
| |
| List<E> getPathFromTarget(GraphNode<E> source, GraphNode<E> target) { |
| List<E> path = <E>[]; |
| GraphNode<E> u = target; |
| while (u == source || prev[u] != null) { |
| path.add(u.node); |
| u = prev[u]; |
| } |
| return path.reversed.toList(); |
| } |
| } |
| |
| class GraphNode<E> { |
| final E node; |
| final Set<GraphNode<E>> outgoing = new Set<GraphNode<E>>(); |
| final Set<GraphNode<E>> incoming = new Set<GraphNode<E>>(); |
| |
| GraphNode(this.node); |
| |
| void addOutgoing(GraphNode<E> other) { |
| if (outgoing.add(other)) { |
| other.incoming.add(this); |
| } |
| } |
| |
| String toString() { |
| return "GraphNode[$node]"; |
| } |
| } |