Skip to main content

rust_igraph/algorithms/traversal/
bfs.rs

1//! Breadth-first search.
2//!
3//! Counterpart of `igraph_bfs()` from `references/igraph/src/properties/bfs.c`.
4//! - Phase 0 [`bfs`]: simplest variant, returns the visit order.
5//! - Phase 1 [`bfs_tree`] (ALGO-TR-001): full multi-output variant,
6//!   returns the visit order + per-vertex distance + BFS-tree parent.
7//! - [`bfs_simple`]: mode-aware BFS with layer boundaries. Counterpart
8//!   of `igraph_bfs_simple` from `references/igraph/src/graph/visitors.c`.
9
10use std::collections::VecDeque;
11
12use crate::core::{Graph, IgraphResult, VertexId};
13
14/// Result of a multi-output BFS scan from a single root. Returned by
15/// [`bfs_tree`].
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct BfsTree {
18    /// Vertices reachable from the root, in BFS visit order.
19    pub order: Vec<VertexId>,
20    /// `distances[v] == Some(d)` if `v` is reachable from the root in
21    /// `d` edges (`d == 0` for the root); `None` if unreachable.
22    pub distances: Vec<Option<u32>>,
23    /// `parents[v] == Some(p)` if `v` was BFS-discovered via `p`; `None`
24    /// for the root and for unreachable vertices.
25    pub parents: Vec<Option<VertexId>>,
26}
27
28/// Multi-output BFS from `root`. Returns visit order, per-vertex
29/// distances, and per-vertex BFS-tree parent in a single pass.
30///
31/// Counterpart of `igraph_bfs(_, root, _, _, &order, _, &father, &dist, _, _)`
32/// — the common subset of the upstream callback-driven variant.
33///
34/// For directed graphs traversal follows out-edges (matching upstream's
35/// `IGRAPH_OUT` mode default). Errors if `root` is out of range.
36///
37/// # Examples
38///
39/// ```
40/// use rust_igraph::{Graph, bfs_tree};
41///
42/// // Tree:
43/// //     0
44/// //    / \
45/// //   1   2
46/// //   |
47/// //   3
48/// let mut g = Graph::with_vertices(4);
49/// g.add_edge(0, 1).unwrap();
50/// g.add_edge(0, 2).unwrap();
51/// g.add_edge(1, 3).unwrap();
52///
53/// let r = bfs_tree(&g, 0).unwrap();
54/// assert_eq!(r.order, vec![0, 1, 2, 3]);
55/// assert_eq!(r.distances, vec![Some(0), Some(1), Some(1), Some(2)]);
56/// assert_eq!(r.parents, vec![None, Some(0), Some(0), Some(1)]);
57/// ```
58pub fn bfs_tree(graph: &Graph, root: VertexId) -> IgraphResult<BfsTree> {
59    graph.neighbors(root)?; // surface VertexOutOfRange
60
61    let n = graph.vcount();
62    let n_us = n as usize;
63    let mut distances: Vec<Option<u32>> = vec![None; n_us];
64    let mut parents: Vec<Option<VertexId>> = vec![None; n_us];
65    let mut order: Vec<VertexId> = Vec::with_capacity(n_us);
66    let mut queue: VecDeque<VertexId> = VecDeque::new();
67
68    distances[root as usize] = Some(0);
69    order.push(root);
70    queue.push_back(root);
71
72    while let Some(v) = queue.pop_front() {
73        let v_dist = distances[v as usize].ok_or(crate::core::IgraphError::Internal(
74            "dequeued vertex has no distance",
75        ))?;
76        let next_dist = v_dist
77            .checked_add(1)
78            .ok_or(crate::core::IgraphError::Internal(
79                "BFS distance overflow (graph diameter exceeds u32::MAX)",
80            ))?;
81        for w in graph.neighbors(v)? {
82            if distances[w as usize].is_none() {
83                distances[w as usize] = Some(next_dist);
84                parents[w as usize] = Some(v);
85                order.push(w);
86                queue.push_back(w);
87            }
88        }
89    }
90
91    Ok(BfsTree {
92        order,
93        distances,
94        parents,
95    })
96}
97
98/// Visit order of vertices reachable from `root`, in BFS order.
99///
100/// Vertices unreachable from `root` are not included. Errors if `root` is not
101/// a valid vertex.
102///
103/// # Examples
104///
105/// ```
106/// use rust_igraph::{Graph, bfs};
107///
108/// // 0 - 1 - 3
109/// // |
110/// // 2
111/// let mut g = Graph::with_vertices(4);
112/// g.add_edge(0, 1).unwrap();
113/// g.add_edge(0, 2).unwrap();
114/// g.add_edge(1, 3).unwrap();
115///
116/// let order = bfs(&g, 0).unwrap();
117/// assert_eq!(order, vec![0, 1, 2, 3]);
118/// ```
119pub fn bfs(graph: &Graph, root: VertexId) -> IgraphResult<Vec<VertexId>> {
120    // Validate root via the neighbor lookup; this surfaces VertexOutOfRange.
121    graph.neighbors(root)?;
122
123    let n = graph.vcount();
124    let mut visited = vec![false; n as usize];
125    let mut order: Vec<VertexId> = Vec::with_capacity(n as usize);
126    let mut queue: VecDeque<VertexId> = VecDeque::new();
127
128    visited[root as usize] = true;
129    order.push(root);
130    queue.push_back(root);
131
132    while let Some(v) = queue.pop_front() {
133        // `neighbors` returns Vec<VertexId> on the new indexed-edgelist
134        // backend (ALGO-CORE-001a); a contiguous slice is not free anymore.
135        for w in graph.neighbors(v)? {
136            if !visited[w as usize] {
137                visited[w as usize] = true;
138                order.push(w);
139                queue.push_back(w);
140            }
141        }
142    }
143
144    Ok(order)
145}
146
147/// Direction mode for [`bfs_simple`].
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum BfsMode {
150    /// Follow outgoing edges (default for directed graphs).
151    Out,
152    /// Follow incoming edges.
153    In,
154    /// Ignore edge direction (always used for undirected graphs).
155    All,
156}
157
158/// Result of [`bfs_simple`].
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct BfsSimple {
161    /// Vertices visited during the traversal, in BFS order.
162    pub order: Vec<VertexId>,
163    /// Layer boundary indices into [`order`](Self::order). Vertices at
164    /// distance `d` from the root are `order[layers[d]..layers[d+1]]`.
165    /// The length is `max_distance + 2`.
166    pub layers: Vec<usize>,
167    /// `parents[v] == Some(p)` if vertex `v` was discovered via `p`;
168    /// `parents[root] == None` (root has no parent).
169    /// `parents[v] == None` for unreachable vertices as well.
170    /// To distinguish root from unreachable, check `order[0] == v`.
171    pub parents: Vec<Option<VertexId>>,
172}
173
174/// Mode-aware BFS from a single root, returning visit order, layer
175/// boundaries, and BFS-tree parents.
176///
177/// Counterpart of `igraph_bfs_simple` from
178/// `references/igraph/src/graph/visitors.c`.
179///
180/// For undirected graphs the `mode` parameter is ignored (edges are
181/// always treated as bidirectional). For directed graphs, [`BfsMode::Out`]
182/// follows outgoing edges, [`BfsMode::In`] follows incoming edges, and
183/// [`BfsMode::All`] ignores direction.
184///
185/// # Examples
186///
187/// ```
188/// use rust_igraph::{Graph, BfsMode, bfs_simple};
189///
190/// let mut g = Graph::new(4, true).unwrap();
191/// g.add_edge(0, 1).unwrap();
192/// g.add_edge(0, 2).unwrap();
193/// g.add_edge(1, 3).unwrap();
194///
195/// let r = bfs_simple(&g, 0, BfsMode::Out).unwrap();
196/// assert_eq!(r.order, vec![0, 1, 2, 3]);
197/// assert_eq!(r.layers, vec![0, 1, 3, 4]);
198/// assert_eq!(r.parents[3], Some(1));
199/// ```
200pub fn bfs_simple(graph: &Graph, root: VertexId, mode: BfsMode) -> IgraphResult<BfsSimple> {
201    graph.neighbors(root)?; // validate root
202
203    let n = graph.vcount() as usize;
204    let use_mode = if graph.is_directed() {
205        mode
206    } else {
207        BfsMode::All
208    };
209
210    let mut visited = vec![false; n];
211    let mut parents: Vec<Option<VertexId>> = vec![None; n];
212    let mut order: Vec<VertexId> = Vec::with_capacity(n);
213    let mut layers: Vec<usize> = Vec::new();
214
215    // Queue stores (vertex, distance) pairs.
216    let mut queue: VecDeque<(VertexId, u32)> = VecDeque::new();
217    let mut last_layer: Option<u32> = None;
218
219    visited[root as usize] = true;
220    order.push(root);
221    layers.push(0); // layer 0 starts at index 0
222    queue.push_back((root, 0));
223
224    while let Some((v, dist)) = queue.pop_front() {
225        let neis = match use_mode {
226            BfsMode::Out => graph.out_neighbors_vec(v)?,
227            BfsMode::In => graph.in_neighbors_vec(v)?,
228            BfsMode::All => {
229                if graph.is_directed() {
230                    let mut combined = graph.out_neighbors_vec(v)?;
231                    combined.extend(graph.in_neighbors_vec(v)?);
232                    combined
233                } else {
234                    graph.neighbors(v)?
235                }
236            }
237        };
238        let next_dist = dist
239            .checked_add(1)
240            .ok_or(crate::core::IgraphError::Internal(
241                "BFS distance overflow (graph diameter exceeds u32::MAX)",
242            ))?;
243        for w in neis {
244            if !visited[w as usize] {
245                visited[w as usize] = true;
246                parents[w as usize] = Some(v);
247                queue.push_back((w, next_dist));
248                if last_layer != Some(next_dist) {
249                    layers.push(order.len());
250                    last_layer = Some(next_dist);
251                }
252                order.push(w);
253            }
254        }
255    }
256
257    // Sentinel: final layer boundary = total visited count.
258    layers.push(order.len());
259
260    Ok(BfsSimple {
261        order,
262        layers,
263        parents,
264    })
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    fn path_graph(n: u32) -> Graph {
272        let mut g = Graph::with_vertices(n);
273        for i in 0..n.saturating_sub(1) {
274            g.add_edge(i, i + 1).unwrap();
275        }
276        g
277    }
278
279    #[test]
280    fn empty_graph_errors_on_any_root() {
281        let g = Graph::with_vertices(0);
282        let err = bfs(&g, 0).unwrap_err();
283        assert!(matches!(
284            err,
285            crate::core::IgraphError::VertexOutOfRange { id: 0, n: 0 }
286        ));
287    }
288
289    #[test]
290    fn single_vertex_visits_self() {
291        let g = Graph::with_vertices(1);
292        assert_eq!(bfs(&g, 0).unwrap(), vec![0]);
293    }
294
295    #[test]
296    fn path_visits_in_order() {
297        let g = path_graph(5);
298        assert_eq!(bfs(&g, 0).unwrap(), vec![0, 1, 2, 3, 4]);
299    }
300
301    #[test]
302    fn bfs_layers_breadth_first_not_depth_first() {
303        // 0 -- 1 -- 3
304        // |
305        // 2
306        let mut g = Graph::with_vertices(4);
307        g.add_edge(0, 1).unwrap();
308        g.add_edge(0, 2).unwrap();
309        g.add_edge(1, 3).unwrap();
310        // BFS order from 0 must visit both 1 and 2 before 3.
311        let order = bfs(&g, 0).unwrap();
312        assert_eq!(order[0], 0);
313        let pos_3 = order.iter().position(|&x| x == 3).unwrap();
314        let pos_1 = order.iter().position(|&x| x == 1).unwrap();
315        let pos_2 = order.iter().position(|&x| x == 2).unwrap();
316        assert!(pos_3 > pos_1 && pos_3 > pos_2);
317    }
318
319    #[test]
320    fn unreachable_vertex_excluded() {
321        let mut g = Graph::with_vertices(3);
322        g.add_edge(0, 1).unwrap();
323        // vertex 2 is isolated.
324        let order = bfs(&g, 0).unwrap();
325        assert_eq!(order, vec![0, 1]);
326    }
327
328    #[test]
329    fn invalid_root_errors() {
330        let g = Graph::with_vertices(2);
331        let err = bfs(&g, 5).unwrap_err();
332        match err {
333            crate::core::IgraphError::VertexOutOfRange { id, n } => {
334                assert_eq!(id, 5);
335                assert_eq!(n, 2);
336            }
337            other => panic!("unexpected error: {other:?}"),
338        }
339    }
340
341    #[test]
342    fn bfs_tree_returns_full_state_for_a_tree() {
343        // Star centred at 0, leaves 1, 2, 3.
344        let mut g = Graph::with_vertices(4);
345        g.add_edge(0, 1).unwrap();
346        g.add_edge(0, 2).unwrap();
347        g.add_edge(0, 3).unwrap();
348        let r = bfs_tree(&g, 0).unwrap();
349        assert_eq!(r.order, vec![0, 1, 2, 3]);
350        assert_eq!(r.distances, vec![Some(0), Some(1), Some(1), Some(1)]);
351        assert_eq!(r.parents, vec![None, Some(0), Some(0), Some(0)]);
352    }
353
354    #[test]
355    fn bfs_tree_path_5_is_chain() {
356        let g = path_graph(5);
357        let r = bfs_tree(&g, 0).unwrap();
358        assert_eq!(r.order, vec![0, 1, 2, 3, 4]);
359        assert_eq!(
360            r.distances,
361            vec![Some(0), Some(1), Some(2), Some(3), Some(4)]
362        );
363        assert_eq!(r.parents, vec![None, Some(0), Some(1), Some(2), Some(3)]);
364    }
365
366    #[test]
367    fn bfs_tree_unreachable_vertices_have_none() {
368        let mut g = Graph::with_vertices(4);
369        g.add_edge(0, 1).unwrap();
370        // 2 and 3 isolated.
371        let r = bfs_tree(&g, 0).unwrap();
372        assert_eq!(r.order, vec![0, 1]);
373        assert_eq!(r.distances, vec![Some(0), Some(1), None, None]);
374        assert_eq!(r.parents, vec![None, Some(0), None, None]);
375    }
376
377    #[test]
378    fn bfs_tree_invalid_root_errors() {
379        let g = Graph::with_vertices(3);
380        assert!(bfs_tree(&g, 7).is_err());
381    }
382
383    #[test]
384    fn bfs_tree_directed_uses_out_edges() {
385        // 0 -> 1 -> 2, 1 -> 3: from 0, all 4 reachable.
386        let mut g = Graph::new(4, true).unwrap();
387        g.add_edge(0, 1).unwrap();
388        g.add_edge(1, 2).unwrap();
389        g.add_edge(1, 3).unwrap();
390        let r = bfs_tree(&g, 0).unwrap();
391        assert_eq!(r.distances, vec![Some(0), Some(1), Some(2), Some(2)]);
392        assert_eq!(r.parents[0], None);
393        assert_eq!(r.parents[1], Some(0));
394        assert_eq!(r.parents[2], Some(1));
395        assert_eq!(r.parents[3], Some(1));
396        // From vertex 2, only 2 is reachable (no out-edges).
397        let r2 = bfs_tree(&g, 2).unwrap();
398        assert_eq!(r2.order, vec![2]);
399    }
400
401    // ── bfs_simple tests ────────────────────────────────────────────
402
403    #[test]
404    fn bfs_simple_undirected_tree() {
405        //     0
406        //    / \
407        //   1   2
408        //   |
409        //   3
410        let mut g = Graph::with_vertices(4);
411        g.add_edge(0, 1).unwrap();
412        g.add_edge(0, 2).unwrap();
413        g.add_edge(1, 3).unwrap();
414
415        let r = bfs_simple(&g, 0, BfsMode::Out).unwrap();
416        assert_eq!(r.order, vec![0, 1, 2, 3]);
417        assert_eq!(r.layers, vec![0, 1, 3, 4]);
418        assert_eq!(r.parents[0], None);
419        assert_eq!(r.parents[1], Some(0));
420        assert_eq!(r.parents[2], Some(0));
421        assert_eq!(r.parents[3], Some(1));
422    }
423
424    #[test]
425    fn bfs_simple_single_vertex() {
426        let g = Graph::with_vertices(1);
427        let r = bfs_simple(&g, 0, BfsMode::All).unwrap();
428        assert_eq!(r.order, vec![0]);
429        assert_eq!(r.layers, vec![0, 1]);
430        assert_eq!(r.parents, vec![None]);
431    }
432
433    #[test]
434    fn bfs_simple_invalid_root() {
435        let g = Graph::with_vertices(2);
436        assert!(bfs_simple(&g, 5, BfsMode::Out).is_err());
437    }
438
439    #[test]
440    fn bfs_simple_directed_out() {
441        // 0 -> 1 -> 2, 0 -> 3
442        let mut g = Graph::new(4, true).unwrap();
443        g.add_edge(0, 1).unwrap();
444        g.add_edge(1, 2).unwrap();
445        g.add_edge(0, 3).unwrap();
446
447        let r = bfs_simple(&g, 0, BfsMode::Out).unwrap();
448        assert_eq!(r.order, vec![0, 1, 3, 2]);
449        assert_eq!(r.layers, vec![0, 1, 3, 4]);
450        assert_eq!(r.parents[1], Some(0));
451        assert_eq!(r.parents[2], Some(1));
452        assert_eq!(r.parents[3], Some(0));
453    }
454
455    #[test]
456    fn bfs_simple_directed_in() {
457        // 1 -> 0, 2 -> 1, 3 -> 0
458        let mut g = Graph::new(4, true).unwrap();
459        g.add_edge(1, 0).unwrap();
460        g.add_edge(2, 1).unwrap();
461        g.add_edge(3, 0).unwrap();
462
463        let r = bfs_simple(&g, 0, BfsMode::In).unwrap();
464        assert_eq!(r.order, vec![0, 1, 3, 2]);
465        assert_eq!(r.layers, vec![0, 1, 3, 4]);
466        assert_eq!(r.parents[1], Some(0));
467        assert_eq!(r.parents[3], Some(0));
468        assert_eq!(r.parents[2], Some(1));
469    }
470
471    #[test]
472    fn bfs_simple_directed_all() {
473        // 0 -> 1, 2 -> 0
474        let mut g = Graph::new(3, true).unwrap();
475        g.add_edge(0, 1).unwrap();
476        g.add_edge(2, 0).unwrap();
477
478        let r = bfs_simple(&g, 0, BfsMode::All).unwrap();
479        assert_eq!(r.order.len(), 3);
480        assert_eq!(r.order[0], 0);
481        // Both 1 and 2 are at distance 1
482        assert_eq!(r.layers, vec![0, 1, 3]);
483    }
484
485    #[test]
486    fn bfs_simple_unreachable_vertices() {
487        let mut g = Graph::with_vertices(4);
488        g.add_edge(0, 1).unwrap();
489        // 2, 3 isolated
490        let r = bfs_simple(&g, 0, BfsMode::All).unwrap();
491        assert_eq!(r.order, vec![0, 1]);
492        assert_eq!(r.layers, vec![0, 1, 2]);
493        assert_eq!(r.parents[2], None);
494        assert_eq!(r.parents[3], None);
495    }
496
497    #[test]
498    fn bfs_simple_mode_ignored_for_undirected() {
499        let mut g = Graph::with_vertices(3);
500        g.add_edge(0, 1).unwrap();
501        g.add_edge(1, 2).unwrap();
502        // BfsMode::In should be ignored for undirected
503        let r = bfs_simple(&g, 0, BfsMode::In).unwrap();
504        assert_eq!(r.order, vec![0, 1, 2]);
505    }
506
507    #[test]
508    fn bfs_simple_layers_match_distances() {
509        let g = path_graph(6);
510        let r = bfs_simple(&g, 0, BfsMode::Out).unwrap();
511        // Path: 0-1-2-3-4-5 => each layer has exactly 1 vertex (except root)
512        assert_eq!(r.layers, vec![0, 1, 2, 3, 4, 5, 6]);
513        for d in 0..6 {
514            let layer_verts = &r.order[r.layers[d]..r.layers[d + 1]];
515            assert_eq!(layer_verts.len(), 1);
516        }
517    }
518}