rust_igraph/algorithms/traversal/
bfs.rs1use std::collections::VecDeque;
9
10use crate::core::{Graph, IgraphResult, VertexId};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct BfsTree {
16 pub order: Vec<VertexId>,
18 pub distances: Vec<Option<u32>>,
21 pub parents: Vec<Option<VertexId>>,
24}
25
26pub fn bfs_tree(graph: &Graph, root: VertexId) -> IgraphResult<BfsTree> {
57 graph.neighbors(root)?; let n = graph.vcount();
60 let n_us = n as usize;
61 let mut distances: Vec<Option<u32>> = vec![None; n_us];
62 let mut parents: Vec<Option<VertexId>> = vec![None; n_us];
63 let mut order: Vec<VertexId> = Vec::with_capacity(n_us);
64 let mut queue: VecDeque<VertexId> = VecDeque::new();
65
66 distances[root as usize] = Some(0);
67 order.push(root);
68 queue.push_back(root);
69
70 while let Some(v) = queue.pop_front() {
71 let v_dist = distances[v as usize].expect("dequeued vertex is visited");
72 let next_dist = v_dist
73 .checked_add(1)
74 .ok_or(crate::core::IgraphError::Internal(
75 "BFS distance overflow (graph diameter exceeds u32::MAX)",
76 ))?;
77 for w in graph.neighbors(v)? {
78 if distances[w as usize].is_none() {
79 distances[w as usize] = Some(next_dist);
80 parents[w as usize] = Some(v);
81 order.push(w);
82 queue.push_back(w);
83 }
84 }
85 }
86
87 Ok(BfsTree {
88 order,
89 distances,
90 parents,
91 })
92}
93
94pub fn bfs(graph: &Graph, root: VertexId) -> IgraphResult<Vec<VertexId>> {
116 graph.neighbors(root)?;
118
119 let n = graph.vcount();
120 let mut visited = vec![false; n as usize];
121 let mut order: Vec<VertexId> = Vec::with_capacity(n as usize);
122 let mut queue: VecDeque<VertexId> = VecDeque::new();
123
124 visited[root as usize] = true;
125 order.push(root);
126 queue.push_back(root);
127
128 while let Some(v) = queue.pop_front() {
129 for w in graph.neighbors(v)? {
132 if !visited[w as usize] {
133 visited[w as usize] = true;
134 order.push(w);
135 queue.push_back(w);
136 }
137 }
138 }
139
140 Ok(order)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 fn path_graph(n: u32) -> Graph {
148 let mut g = Graph::with_vertices(n);
149 for i in 0..n.saturating_sub(1) {
150 g.add_edge(i, i + 1).unwrap();
151 }
152 g
153 }
154
155 #[test]
156 fn empty_graph_errors_on_any_root() {
157 let g = Graph::with_vertices(0);
158 let err = bfs(&g, 0).unwrap_err();
159 assert!(matches!(
160 err,
161 crate::core::IgraphError::VertexOutOfRange { id: 0, n: 0 }
162 ));
163 }
164
165 #[test]
166 fn single_vertex_visits_self() {
167 let g = Graph::with_vertices(1);
168 assert_eq!(bfs(&g, 0).unwrap(), vec![0]);
169 }
170
171 #[test]
172 fn path_visits_in_order() {
173 let g = path_graph(5);
174 assert_eq!(bfs(&g, 0).unwrap(), vec![0, 1, 2, 3, 4]);
175 }
176
177 #[test]
178 fn bfs_layers_breadth_first_not_depth_first() {
179 let mut g = Graph::with_vertices(4);
183 g.add_edge(0, 1).unwrap();
184 g.add_edge(0, 2).unwrap();
185 g.add_edge(1, 3).unwrap();
186 let order = bfs(&g, 0).unwrap();
188 assert_eq!(order[0], 0);
189 let pos_3 = order.iter().position(|&x| x == 3).unwrap();
190 let pos_1 = order.iter().position(|&x| x == 1).unwrap();
191 let pos_2 = order.iter().position(|&x| x == 2).unwrap();
192 assert!(pos_3 > pos_1 && pos_3 > pos_2);
193 }
194
195 #[test]
196 fn unreachable_vertex_excluded() {
197 let mut g = Graph::with_vertices(3);
198 g.add_edge(0, 1).unwrap();
199 let order = bfs(&g, 0).unwrap();
201 assert_eq!(order, vec![0, 1]);
202 }
203
204 #[test]
205 fn invalid_root_errors() {
206 let g = Graph::with_vertices(2);
207 let err = bfs(&g, 5).unwrap_err();
208 match err {
209 crate::core::IgraphError::VertexOutOfRange { id, n } => {
210 assert_eq!(id, 5);
211 assert_eq!(n, 2);
212 }
213 other => panic!("unexpected error: {other:?}"),
214 }
215 }
216
217 #[test]
218 fn bfs_tree_returns_full_state_for_a_tree() {
219 let mut g = Graph::with_vertices(4);
221 g.add_edge(0, 1).unwrap();
222 g.add_edge(0, 2).unwrap();
223 g.add_edge(0, 3).unwrap();
224 let r = bfs_tree(&g, 0).unwrap();
225 assert_eq!(r.order, vec![0, 1, 2, 3]);
226 assert_eq!(r.distances, vec![Some(0), Some(1), Some(1), Some(1)]);
227 assert_eq!(r.parents, vec![None, Some(0), Some(0), Some(0)]);
228 }
229
230 #[test]
231 fn bfs_tree_path_5_is_chain() {
232 let g = path_graph(5);
233 let r = bfs_tree(&g, 0).unwrap();
234 assert_eq!(r.order, vec![0, 1, 2, 3, 4]);
235 assert_eq!(
236 r.distances,
237 vec![Some(0), Some(1), Some(2), Some(3), Some(4)]
238 );
239 assert_eq!(r.parents, vec![None, Some(0), Some(1), Some(2), Some(3)]);
240 }
241
242 #[test]
243 fn bfs_tree_unreachable_vertices_have_none() {
244 let mut g = Graph::with_vertices(4);
245 g.add_edge(0, 1).unwrap();
246 let r = bfs_tree(&g, 0).unwrap();
248 assert_eq!(r.order, vec![0, 1]);
249 assert_eq!(r.distances, vec![Some(0), Some(1), None, None]);
250 assert_eq!(r.parents, vec![None, Some(0), None, None]);
251 }
252
253 #[test]
254 fn bfs_tree_invalid_root_errors() {
255 let g = Graph::with_vertices(3);
256 assert!(bfs_tree(&g, 7).is_err());
257 }
258
259 #[test]
260 fn bfs_tree_directed_uses_out_edges() {
261 let mut g = Graph::new(4, true).unwrap();
263 g.add_edge(0, 1).unwrap();
264 g.add_edge(1, 2).unwrap();
265 g.add_edge(1, 3).unwrap();
266 let r = bfs_tree(&g, 0).unwrap();
267 assert_eq!(r.distances, vec![Some(0), Some(1), Some(2), Some(2)]);
268 assert_eq!(r.parents[0], None);
269 assert_eq!(r.parents[1], Some(0));
270 assert_eq!(r.parents[2], Some(1));
271 assert_eq!(r.parents[3], Some(1));
272 let r2 = bfs_tree(&g, 2).unwrap();
274 assert_eq!(r2.order, vec![2]);
275 }
276}