8
8
//! Part two is a a BFS *backwards* from the end to the finish, tracing the cost exactly
9
9
//! to find all possible paths. This reuses the cost information from the Dijkstra without
10
10
//! requiring any extra state keeping for the paths.
11
- //!
12
- //! To speed things up even further we use a trick. Classic Dijkstra uses a generic priority queue
13
- //! that can be implemented in Rust using a [`BinaryHeap`]. However the total cost follows a
14
- //! strictly increasing order in a constrained range of values, so we can use a much faster
15
- //! [bucket queue](https://en.wikipedia.org/wiki/Bucket_queue). The maximum possible increase is
16
- //! 1000 so we need 1001 buckets.
17
- //!
18
- //! [`BinaryHeap`]: std::collections::BinaryHeap
19
11
use crate :: util:: grid:: * ;
20
12
use crate :: util:: point:: * ;
21
13
use std:: collections:: VecDeque ;
@@ -30,25 +22,26 @@ pub fn parse(input: &str) -> Input {
30
22
let start = grid. find ( b'S' ) . unwrap ( ) ;
31
23
let end = grid. find ( b'E' ) . unwrap ( ) ;
32
24
33
- // Forwards Dijkstra
34
- let mut buckets = vec ! [ Vec :: new( ) ; 1001 ] ;
25
+ // Forwards Dijkstra. Since turns are so much more expensive than moving forward, we can
26
+ // treat this as a glorified BFS using two priority queues. This is much faster than using
27
+ // an actual min heap.
28
+ let mut todo_first = VecDeque :: new ( ) ;
29
+ let mut todo_second = VecDeque :: new ( ) ;
35
30
// State is `(position, direction)`.
36
31
let mut seen = grid. same_size_with ( [ u32:: MAX ; 4 ] ) ;
37
- let mut cost = 0 ;
38
32
let mut lowest = u32:: MAX ;
39
33
40
- buckets [ 0 ] . push ( ( start, 0 ) ) ;
34
+ todo_first . push_back ( ( start, 0 , 0 ) ) ;
41
35
seen[ start] [ 0 ] = 0 ;
42
36
43
- while lowest == u32:: MAX {
44
- let index = ( cost % 1001 ) as usize ;
45
-
46
- while let Some ( ( position, direction) ) = buckets[ index] . pop ( ) {
47
- // Once we find the end node then stop. All paths of the same cost must be in
48
- // this bucket, so have already been accounted for.
37
+ while !todo_first. is_empty ( ) {
38
+ while let Some ( ( position, direction, cost) ) = todo_first. pop_front ( ) {
39
+ if cost >= lowest {
40
+ continue ;
41
+ }
49
42
if position == end {
50
43
lowest = cost;
51
- break ;
44
+ continue ;
52
45
}
53
46
54
47
// -1.rem_euclid(4) = 3
@@ -60,17 +53,20 @@ pub fn parse(input: &str) -> Input {
60
53
( position, right, cost + 1000 ) ,
61
54
] ;
62
55
63
- for ( next_position, next_direction, next_cost) in next {
56
+ for tuple @ ( next_position, next_direction, next_cost) in next {
64
57
if grid[ next_position] != b'#' && next_cost < seen[ next_position] [ next_direction] {
65
58
// Find the next bucket.
66
- let index = ( next_cost % 1001 ) as usize ;
67
- buckets[ index] . push ( ( next_position, next_direction) ) ;
59
+ if next_direction == direction {
60
+ todo_first. push_back ( tuple) ;
61
+ } else {
62
+ todo_second. push_back ( tuple) ;
63
+ }
68
64
seen[ next_position] [ next_direction] = next_cost;
69
65
}
70
66
}
71
67
}
72
68
73
- cost += 1 ;
69
+ ( todo_first , todo_second ) = ( todo_second , todo_first ) ;
74
70
}
75
71
76
72
// Backwards BFS
0 commit comments