algorithms_edu/algo/graph/shortest_path/
dijkstra.rs

1//! This mod contains an implementation of Dijkstra's shortest path algorithm from a start node to a
2//! specific ending node. Dijkstra can also be modified to find the shortest path between a starting
3//! node and all other nodes in the graph. However, in this implementation since we're only going
4//! from a starting node to an ending node we can employ an optimization to stop early once we've
5//! visited all the neighbors of the ending node.
6//!
7//! # Resources
8//!
9//! - [W. Fiset's video](https://www.youtube.com/watch?v=pSqmAO-m7Lk&list=PLDV1Zeh2NRsDGO4--qE8yH72HFL1Km93P&index=18)
10
11use crate::algo::graph::{Edge, WeightedAdjacencyList};
12use ordered_float::OrderedFloat;
13use priority_queue::PriorityQueue;
14
15impl WeightedAdjacencyList {
16    pub fn dijkstra(&self, start: usize, end: usize) -> Option<(f64, Vec<usize>)> {
17        let n = self.node_count();
18        let mut dists = vec![f64::INFINITY; n];
19        let mut prev = vec![None; n];
20        let mut vis = vec![false; n];
21        let mut pq = PriorityQueue::with_capacity(self.node_count());
22        // `priority_queue::PriorityQueue` requires that the priority implements `Ord`,
23        // but the std floats implement only `PartialOrd`
24        pq.push(start, OrderedFloat::from(-0f64));
25        dists[start] = 0f64;
26        while let Some((node, cur_dist)) = pq.pop() {
27            // Once we've visited all the nodes spanning from the end
28            // node we know we can return the minimum distance value to
29            // the end node because it cannot get any better after this point.
30            if node == end {
31                break;
32            };
33            // `priority_queue::PriorityQueue` is a max priority queue, but we want the min.
34            // Negating the priority (dist) immediately before pushing and after popping will do.
35            let cur_dist = -cur_dist.into_inner();
36
37            vis[node] = true;
38
39            let dist = &mut dists[node];
40
41            // We already found a better path before we got to
42            // processing this node so we can ignore it.
43            if *dist < cur_dist {
44                continue;
45            }
46            *dist = cur_dist;
47            for &Edge { to, weight } in &self[node] {
48                // You cannot get a shorter path by revisiting
49                // a node you have already visited before.
50                if !vis[to] {
51                    // Relax edge by updating minimum cost if applicable.
52                    let new_dist = cur_dist + weight;
53                    if new_dist < dists[to] {
54                        prev[to] = Some(node);
55                        dists[to] = new_dist;
56                        pq.push(to, (-new_dist).into());
57                    }
58                }
59            }
60        }
61
62        if prev[end].is_none() {
63            if start == end {
64                Some((dists[start], vec![start]))
65            } else {
66                None
67            }
68        } else {
69            // reconstruct path
70            let mut path = vec![end];
71            let mut i = end;
72            while let Some(node) = prev[i] {
73                path.push(node);
74                i = node;
75            }
76            path.reverse();
77            Some((dists[end], path))
78        }
79    }
80}
81#[cfg(test)]
82mod tests {
83
84    use super::*;
85    #[test]
86    fn test_dijkstra() {
87        // example from https://www.youtube.com/watch?v=pSqmAO-m7Lk&list=PLDV1Zeh2NRsDGO4--qE8yH72HFL1Km93P&index=18
88        // at 16:47
89        let graph = WeightedAdjacencyList::new_directed(
90            6,
91            &[
92                (0, 1, 5.),
93                (0, 2, 1.),
94                (1, 2, 2.),
95                (2, 1, 3.),
96                (1, 3, 3.),
97                (1, 4, 20.),
98                (2, 4, 12.),
99                (3, 2, 3.),
100                (3, 4, 2.),
101                (3, 5, 6.),
102                (3, 4, 2.),
103                (4, 5, 1.),
104            ],
105        );
106        let (dist, path) = graph.dijkstra(0, 5).unwrap();
107        assert_eq!(&path, &[0, 2, 1, 3, 4, 5]);
108        assert_eq!(dist, 10.);
109        assert_eq!(graph.dijkstra(1, 1).unwrap(), (0.0, vec![1]));
110    }
111}