Skip to main content

oxihuman_core/
graph_search.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! BFS/DFS graph search on adjacency list graphs.
6
7use std::collections::{HashSet, VecDeque};
8
9/// An adjacency-list directed graph.
10pub struct AdjGraph {
11    adj: Vec<Vec<usize>>,
12}
13
14/// Construct a new graph with `n` vertices.
15pub fn new_adj_graph(n: usize) -> AdjGraph {
16    AdjGraph {
17        adj: vec![Vec::new(); n],
18    }
19}
20
21impl AdjGraph {
22    /// Add a directed edge from `u` to `v`.
23    pub fn add_edge(&mut self, u: usize, v: usize) {
24        if u < self.adj.len() {
25            self.adj[u].push(v);
26        }
27    }
28
29    /// Add an undirected edge.
30    pub fn add_undirected(&mut self, u: usize, v: usize) {
31        self.add_edge(u, v);
32        self.add_edge(v, u);
33    }
34
35    /// Number of vertices.
36    pub fn vertex_count(&self) -> usize {
37        self.adj.len()
38    }
39
40    /// Number of (directed) edges.
41    pub fn edge_count(&self) -> usize {
42        self.adj.iter().map(|v| v.len()).sum()
43    }
44
45    /// BFS from `start`; returns visited vertices in BFS order.
46    pub fn bfs(&self, start: usize) -> Vec<usize> {
47        let n = self.adj.len();
48        if start >= n {
49            return Vec::new();
50        }
51        let mut visited = vec![false; n];
52        let mut order = Vec::new();
53        let mut queue = VecDeque::new();
54        visited[start] = true;
55        queue.push_back(start);
56        while let Some(u) = queue.pop_front() {
57            order.push(u);
58            for &v in &self.adj[u] {
59                if v < n && !visited[v] {
60                    visited[v] = true;
61                    queue.push_back(v);
62                }
63            }
64        }
65        order
66    }
67
68    /// DFS from `start`; returns visited vertices in DFS order.
69    pub fn dfs(&self, start: usize) -> Vec<usize> {
70        let n = self.adj.len();
71        if start >= n {
72            return Vec::new();
73        }
74        let mut visited = vec![false; n];
75        let mut order = Vec::new();
76        self.dfs_rec(start, &mut visited, &mut order);
77        order
78    }
79
80    fn dfs_rec(&self, u: usize, visited: &mut Vec<bool>, order: &mut Vec<usize>) {
81        visited[u] = true;
82        order.push(u);
83        for &v in &self.adj[u] {
84            if v < self.adj.len() && !visited[v] {
85                self.dfs_rec(v, visited, order);
86            }
87        }
88    }
89
90    /// BFS shortest path from `src` to `dst` (unweighted). Returns `None` if unreachable.
91    pub fn shortest_path(&self, src: usize, dst: usize) -> Option<Vec<usize>> {
92        let n = self.adj.len();
93        if src >= n || dst >= n {
94            return None;
95        }
96        let mut prev = vec![usize::MAX; n];
97        let mut visited = vec![false; n];
98        let mut queue = VecDeque::new();
99        visited[src] = true;
100        queue.push_back(src);
101        while let Some(u) = queue.pop_front() {
102            if u == dst {
103                let mut path = Vec::new();
104                let mut cur = dst;
105                while cur != src {
106                    path.push(cur);
107                    cur = prev[cur];
108                }
109                path.push(src);
110                path.reverse();
111                return Some(path);
112            }
113            for &v in &self.adj[u] {
114                if v < n && !visited[v] {
115                    visited[v] = true;
116                    prev[v] = u;
117                    queue.push_back(v);
118                }
119            }
120        }
121        None
122    }
123
124    /// Check if `dst` is reachable from `src` via BFS.
125    pub fn is_reachable(&self, src: usize, dst: usize) -> bool {
126        let visited: HashSet<usize> = self.bfs(src).into_iter().collect();
127        visited.contains(&dst)
128    }
129
130    /// Return the BFS level (distance) of each vertex from `start`.
131    pub fn bfs_levels(&self, start: usize) -> Vec<Option<usize>> {
132        let n = self.adj.len();
133        let mut levels = vec![None; n];
134        if start >= n {
135            return levels;
136        }
137        levels[start] = Some(0);
138        let mut queue = VecDeque::new();
139        queue.push_back(start);
140        while let Some(u) = queue.pop_front() {
141            let lvl = levels[u].unwrap_or(0);
142            for &v in &self.adj[u] {
143                if v < n && levels[v].is_none() {
144                    levels[v] = Some(lvl + 1);
145                    queue.push_back(v);
146                }
147            }
148        }
149        levels
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_new_graph() {
159        /* new_adj_graph creates graph with correct vertex count */
160        let g = new_adj_graph(5);
161        assert_eq!(g.vertex_count(), 5);
162        assert_eq!(g.edge_count(), 0);
163    }
164
165    #[test]
166    fn test_add_edge() {
167        /* add_edge increases edge count */
168        let mut g = new_adj_graph(3);
169        g.add_edge(0, 1);
170        g.add_edge(1, 2);
171        assert_eq!(g.edge_count(), 2);
172    }
173
174    #[test]
175    fn test_bfs_order() {
176        /* BFS visits vertices level-by-level */
177        let mut g = new_adj_graph(4);
178        g.add_undirected(0, 1);
179        g.add_undirected(0, 2);
180        g.add_undirected(1, 3);
181        let order = g.bfs(0);
182        assert_eq!(order[0], 0);
183        assert!(order.contains(&1));
184        assert!(order.contains(&3));
185    }
186
187    #[test]
188    fn test_dfs_visits_all() {
189        /* DFS from 0 visits all connected vertices */
190        let mut g = new_adj_graph(5);
191        for i in 0..4 {
192            g.add_undirected(i, i + 1);
193        }
194        let order = g.dfs(0);
195        assert_eq!(order.len(), 5);
196    }
197
198    #[test]
199    fn test_shortest_path_found() {
200        /* shortest_path finds a path between connected vertices */
201        let mut g = new_adj_graph(5);
202        g.add_undirected(0, 1);
203        g.add_undirected(1, 2);
204        g.add_undirected(2, 3);
205        let path = g.shortest_path(0, 3).expect("should succeed");
206        assert_eq!(path[0], 0);
207        assert_eq!(*path.last().expect("should succeed"), 3);
208    }
209
210    #[test]
211    fn test_shortest_path_none() {
212        /* shortest_path returns None for disconnected vertices */
213        let mut g = new_adj_graph(4);
214        g.add_edge(0, 1);
215        assert!(g.shortest_path(0, 3).is_none());
216    }
217
218    #[test]
219    fn test_is_reachable() {
220        /* is_reachable returns true for connected, false for disconnected */
221        let mut g = new_adj_graph(4);
222        g.add_undirected(0, 1);
223        g.add_undirected(1, 2);
224        assert!(g.is_reachable(0, 2));
225        assert!(!g.is_reachable(0, 3));
226    }
227
228    #[test]
229    fn test_bfs_levels() {
230        /* bfs_levels correctly computes distances from source */
231        let mut g = new_adj_graph(5);
232        g.add_undirected(0, 1);
233        g.add_undirected(1, 2);
234        g.add_undirected(2, 3);
235        let levels = g.bfs_levels(0);
236        assert_eq!(levels[0], Some(0));
237        assert_eq!(levels[1], Some(1));
238        assert_eq!(levels[3], Some(3));
239        assert_eq!(levels[4], None);
240    }
241
242    #[test]
243    fn test_add_undirected() {
244        /* add_undirected creates two directed edges */
245        let mut g = new_adj_graph(2);
246        g.add_undirected(0, 1);
247        assert_eq!(g.edge_count(), 2);
248    }
249}