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