oxicuda_graphalg/shortest_path/
zero_one_bfs.rs1use std::collections::VecDeque;
19
20use crate::error::{GraphalgError, GraphalgResult};
21use crate::repr::weighted_graph::WeightedGraph;
22
23#[derive(Debug, Clone)]
25pub struct ZeroOneBfsOutput {
26 pub dist: Vec<u64>,
28 pub parent: Vec<usize>,
30}
31
32pub fn zero_one_bfs(g: &WeightedGraph, source: usize) -> GraphalgResult<ZeroOneBfsOutput> {
41 if source >= g.n {
42 return Err(GraphalgError::SourceOutOfRange {
43 node: source,
44 n: g.n,
45 });
46 }
47 for (u, adj) in g.edges.iter().enumerate() {
49 for &(v, w) in adj {
50 let is_zero = w.abs() < 1e-9;
51 let is_one = (w - 1.0).abs() < 1e-9;
52 if !(is_zero || is_one) {
53 return Err(GraphalgError::InvalidEdgeWeight(format!(
54 "edge ({u},{v}) weight {w} is not 0 or 1"
55 )));
56 }
57 }
58 }
59
60 let n = g.n;
61 let mut dist = vec![u64::MAX; n];
62 let mut parent = vec![usize::MAX; n];
63 dist[source] = 0;
64 parent[source] = source;
65
66 let mut dq: VecDeque<usize> = VecDeque::new();
67 dq.push_front(source);
68
69 while let Some(u) = dq.pop_front() {
70 let du = dist[u];
71 for &(v, w) in g.neighbors(u)? {
72 let weight = if w < 0.5 { 0u64 } else { 1u64 };
74 let nd = du.saturating_add(weight);
75 if nd < dist[v] {
76 dist[v] = nd;
77 parent[v] = u;
78 if weight == 0 {
79 dq.push_front(v);
80 } else {
81 dq.push_back(v);
82 }
83 }
84 }
85 }
86
87 Ok(ZeroOneBfsOutput { dist, parent })
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn all_ones_is_plain_bfs() {
96 let mut g = WeightedGraph::new(4);
98 g.add_edge(0, 1, 1.0).expect("ok");
99 g.add_edge(1, 2, 1.0).expect("ok");
100 g.add_edge(2, 3, 1.0).expect("ok");
101 let out = zero_one_bfs(&g, 0).expect("ok");
102 assert_eq!(out.dist, vec![0, 1, 2, 3]);
103 }
104
105 #[test]
106 fn zero_edge_is_free() {
107 let mut g = WeightedGraph::new(3);
109 g.add_edge(0, 1, 1.0).expect("ok");
110 g.add_edge(1, 2, 0.0).expect("ok");
111 let out = zero_one_bfs(&g, 0).expect("ok");
112 assert_eq!(out.dist[1], 1);
113 assert_eq!(out.dist[2], 1, "0-weight edge should be free");
114 }
115
116 #[test]
117 fn prefers_cheaper_zero_path() {
118 let mut g = WeightedGraph::new(4);
121 g.add_edge(0, 3, 1.0).expect("ok");
122 g.add_edge(0, 1, 0.0).expect("ok");
123 g.add_edge(1, 2, 0.0).expect("ok");
124 g.add_edge(2, 3, 0.0).expect("ok");
125 let out = zero_one_bfs(&g, 0).expect("ok");
126 assert_eq!(out.dist[3], 0, "should take the all-zero path");
127 }
128
129 #[test]
130 fn source_distance_zero() {
131 let mut g = WeightedGraph::new(2);
132 g.add_edge(0, 1, 1.0).expect("ok");
133 let out = zero_one_bfs(&g, 0).expect("ok");
134 assert_eq!(out.dist[0], 0);
135 assert_eq!(out.parent[0], 0);
136 }
137
138 #[test]
139 fn unreachable_is_max() {
140 let mut g = WeightedGraph::new(3);
141 g.add_edge(0, 1, 1.0).expect("ok");
142 let out = zero_one_bfs(&g, 0).expect("ok");
143 assert_eq!(out.dist[2], u64::MAX);
144 assert_eq!(out.parent[2], usize::MAX);
145 }
146
147 #[test]
148 fn rejects_non_binary_weight() {
149 let mut g = WeightedGraph::new(2);
150 g.add_edge(0, 1, 2.0).expect("ok");
151 assert!(matches!(
152 zero_one_bfs(&g, 0).unwrap_err(),
153 GraphalgError::InvalidEdgeWeight(_)
154 ));
155 }
156
157 #[test]
158 fn source_out_of_range() {
159 let g = WeightedGraph::new(3);
160 assert!(matches!(
161 zero_one_bfs(&g, 9).unwrap_err(),
162 GraphalgError::SourceOutOfRange { .. }
163 ));
164 }
165
166 #[test]
167 fn matches_unit_bfs_on_grid() {
168 let mut g = WeightedGraph::new(5);
171 g.add_undirected_edge(0, 1, 1.0).expect("ok");
172 g.add_undirected_edge(1, 2, 1.0).expect("ok");
173 g.add_undirected_edge(2, 3, 1.0).expect("ok");
174 g.add_undirected_edge(3, 4, 1.0).expect("ok");
175 g.add_undirected_edge(4, 0, 1.0).expect("ok");
176 let out = zero_one_bfs(&g, 0).expect("ok");
177 assert_eq!(out.dist, vec![0, 1, 2, 2, 1]);
178 }
179
180 #[test]
181 fn mixed_weights_correct() {
182 let mut g = WeightedGraph::new(5);
184 g.add_edge(0, 1, 0.0).expect("ok");
185 g.add_edge(1, 2, 1.0).expect("ok");
186 g.add_edge(2, 3, 0.0).expect("ok");
187 g.add_edge(3, 4, 1.0).expect("ok");
188 let out = zero_one_bfs(&g, 0).expect("ok");
189 assert_eq!(out.dist, vec![0, 0, 1, 1, 2]);
190 }
191
192 #[test]
193 fn parent_chain_reconstructs_path() {
194 let mut g = WeightedGraph::new(4);
195 g.add_edge(0, 1, 1.0).expect("ok");
196 g.add_edge(1, 2, 1.0).expect("ok");
197 g.add_edge(2, 3, 1.0).expect("ok");
198 let out = zero_one_bfs(&g, 0).expect("ok");
199 let mut path = vec![3usize];
201 let mut cur = 3usize;
202 while cur != 0 {
203 cur = out.parent[cur];
204 path.push(cur);
205 }
206 path.reverse();
207 assert_eq!(path, vec![0, 1, 2, 3]);
208 }
209
210 #[test]
211 fn deterministic() {
212 let mut g = WeightedGraph::new(4);
213 g.add_edge(0, 1, 0.0).expect("ok");
214 g.add_edge(0, 2, 1.0).expect("ok");
215 g.add_edge(1, 3, 1.0).expect("ok");
216 g.add_edge(2, 3, 0.0).expect("ok");
217 let a = zero_one_bfs(&g, 0).expect("ok");
218 let b = zero_one_bfs(&g, 0).expect("ok");
219 assert_eq!(a.dist, b.dist);
220 }
221
222 #[test]
223 fn self_loop_ignored() {
224 let mut g = WeightedGraph::new(2);
226 g.add_edge(0, 0, 0.0).expect("ok");
227 g.add_edge(0, 1, 1.0).expect("ok");
228 let out = zero_one_bfs(&g, 0).expect("ok");
229 assert_eq!(out.dist[0], 0);
230 assert_eq!(out.dist[1], 1);
231 }
232}