rust_igraph/algorithms/paths/
eulerian_construct.rs1use crate::algorithms::paths::eulerian::is_eulerian;
13use crate::core::graph::EdgeId;
14use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
15
16pub fn eulerian_path(graph: &Graph) -> IgraphResult<Option<Vec<EdgeId>>> {
54 let cls = is_eulerian(graph)?;
55 if !cls.has_path {
56 return Ok(None);
57 }
58
59 let n = graph.vcount();
60 let m = graph.ecount();
61 if m == 0 {
62 return Ok(Some(Vec::new()));
64 }
65
66 let n_us = n as usize;
67 let m_us = m;
68 let directed = graph.is_directed();
69
70 let mut inc: Vec<Vec<EdgeId>> = Vec::with_capacity(n_us);
76 for v in 0..n {
77 let raw = graph.incident(v)?;
78 if directed {
79 inc.push(raw);
81 } else {
82 let mut seen: std::collections::HashSet<EdgeId> = std::collections::HashSet::new();
83 let mut out: Vec<EdgeId> = Vec::with_capacity(raw.len());
84 for e in raw {
85 if seen.insert(e) {
86 out.push(e);
87 }
88 }
89 inc.push(out);
90 }
91 }
92
93 let mut visited: Vec<bool> = vec![false; m_us];
98 let mut next_idx: Vec<usize> = vec![0; n_us];
99
100 let start_of_path = pick_start_vertex(graph, cls)?;
104
105 let mut tracker: Vec<VertexId> = Vec::with_capacity(n_us);
108 let mut edge_tracker: Vec<EdgeId> = Vec::with_capacity(m_us);
109 let mut path: Vec<VertexId> = Vec::with_capacity(n_us);
110 let mut edge_path: Vec<EdgeId> = Vec::with_capacity(m_us);
111
112 tracker.push(start_of_path);
113 let mut curr = start_of_path;
114
115 loop {
116 let curr_us = curr as usize;
118 while next_idx[curr_us] < inc[curr_us].len()
120 && visited[inc[curr_us][next_idx[curr_us]] as usize]
121 {
122 next_idx[curr_us] += 1;
123 }
124 if next_idx[curr_us] < inc[curr_us].len() {
125 let edge = inc[curr_us][next_idx[curr_us]];
126 visited[edge as usize] = true;
127 next_idx[curr_us] += 1;
128 tracker.push(curr);
129 edge_tracker.push(edge);
130 curr = if directed {
131 graph.edge_target(edge)?
133 } else {
134 graph.edge_other(edge, curr)?
135 };
136 } else {
137 path.push(curr);
139 if let Some(prev) = tracker.pop() {
140 if let Some(curr_e) = edge_tracker.pop() {
141 edge_path.push(curr_e);
142 }
143 curr = prev;
144 } else {
145 break;
146 }
147 }
148 }
149
150 edge_path.reverse();
154 let _ = path; Ok(Some(edge_path))
157}
158
159fn pick_start_vertex(
160 graph: &Graph,
161 cls: crate::algorithms::paths::eulerian::EulerianClassification,
162) -> IgraphResult<VertexId> {
163 let n = graph.vcount();
164 let directed = graph.is_directed();
165 if cls.has_cycle {
166 for v in 0..n {
168 let has_edges = if directed {
169 !graph.incident(v)?.is_empty()
170 } else {
171 !graph.neighbors(v)?.is_empty()
172 };
173 if has_edges {
174 return Ok(v);
175 }
176 }
177 Err(IgraphError::Internal("no edges but cls.has_cycle"))
179 } else if directed {
180 for v in 0..n {
183 let out_deg = graph.incident(v)?.len();
184 let in_deg = compute_in_degree(graph, v)?;
186 if out_deg > in_deg && (out_deg - in_deg) == 1 {
187 return Ok(v);
188 }
189 }
190 Err(IgraphError::Internal(
191 "directed path: no vertex with outgoing_excess == 1",
192 ))
193 } else {
194 for v in 0..n {
196 let deg = graph.degree(v)?;
197 if deg % 2 != 0 {
198 return Ok(v);
199 }
200 }
201 Err(IgraphError::Internal(
202 "has_path && !has_cycle but no odd-degree vertex found",
203 ))
204 }
205}
206
207fn compute_in_degree(graph: &Graph, v: VertexId) -> IgraphResult<usize> {
208 let mut count = 0usize;
212 let m = u32::try_from(graph.ecount()).expect("edge count fits in u32");
213 for e in 0..m {
214 if graph.edge_target(e)? == v {
215 count += 1;
216 }
217 }
218 Ok(count)
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 fn walk_validates(graph: &Graph, walk: &[EdgeId]) -> bool {
226 let mut seen: Vec<bool> = vec![false; graph.ecount()];
228 for &edge in walk {
229 let idx = edge as usize;
230 if idx >= graph.ecount() || seen[idx] {
231 return false;
232 }
233 seen[idx] = true;
234 }
235 if walk.len() < 2 {
237 return true;
238 }
239 for i in 0..walk.len() - 1 {
240 let (a, b) = graph.edge(walk[i]).unwrap();
241 let (c, d) = graph.edge(walk[i + 1]).unwrap();
242 if !(a == c || a == d || b == c || b == d) {
243 return false;
244 }
245 }
246 true
247 }
248
249 #[test]
250 fn empty_graph_returns_empty_walk() {
251 let g = Graph::with_vertices(0);
252 assert_eq!(eulerian_path(&g).unwrap(), Some(Vec::new()));
253 }
254
255 #[test]
256 fn isolated_vertices_return_empty_walk() {
257 let g = Graph::with_vertices(5);
258 assert_eq!(eulerian_path(&g).unwrap(), Some(Vec::new()));
259 }
260
261 #[test]
262 fn triangle_yields_three_edge_walk() {
263 let mut g = Graph::with_vertices(3);
264 g.add_edge(0, 1).unwrap();
265 g.add_edge(1, 2).unwrap();
266 g.add_edge(2, 0).unwrap();
267 let walk = eulerian_path(&g).unwrap().unwrap();
268 assert_eq!(walk.len(), 3);
269 assert!(walk_validates(&g, &walk));
270 }
271
272 #[test]
273 fn path_3_yields_two_edge_walk() {
274 let mut g = Graph::with_vertices(3);
275 g.add_edge(0, 1).unwrap();
276 g.add_edge(1, 2).unwrap();
277 let walk = eulerian_path(&g).unwrap().unwrap();
278 assert_eq!(walk.len(), 2);
279 assert!(walk_validates(&g, &walk));
280 }
281
282 #[test]
283 fn k4_has_no_eulerian_walk() {
284 let mut g = Graph::with_vertices(4);
285 for u in 0..4u32 {
286 for v in (u + 1)..4 {
287 g.add_edge(u, v).unwrap();
288 }
289 }
290 assert_eq!(eulerian_path(&g).unwrap(), None);
291 }
292
293 #[test]
294 fn disconnected_components_no_walk() {
295 let mut g = Graph::with_vertices(4);
296 g.add_edge(0, 1).unwrap();
297 g.add_edge(2, 3).unwrap();
298 assert_eq!(eulerian_path(&g).unwrap(), None);
299 }
300
301 #[test]
302 fn ring_5_walk_visits_all_edges() {
303 let mut g = Graph::with_vertices(5);
304 for i in 0..5u32 {
305 g.add_edge(i, (i + 1) % 5).unwrap();
306 }
307 let walk = eulerian_path(&g).unwrap().unwrap();
308 assert_eq!(walk.len(), 5);
309 assert!(walk_validates(&g, &walk));
310 }
311
312 #[test]
313 fn directed_3_cycle_yields_3_edge_walk() {
314 let mut g = Graph::new(3, true).unwrap();
316 g.add_edge(0, 1).unwrap();
317 g.add_edge(1, 2).unwrap();
318 g.add_edge(2, 0).unwrap();
319 let walk = eulerian_path(&g).unwrap().unwrap();
320 assert_eq!(walk.len(), 3);
321 }
322
323 #[test]
324 fn directed_path_yields_chain_walk() {
325 let mut g = Graph::new(3, true).unwrap();
327 g.add_edge(0, 1).unwrap();
328 g.add_edge(1, 2).unwrap();
329 let walk = eulerian_path(&g).unwrap().unwrap();
330 assert_eq!(walk, vec![0, 1]); }
332
333 #[test]
334 fn directed_no_eulerian_returns_none() {
335 let mut g = Graph::new(3, true).unwrap();
337 g.add_edge(0, 1).unwrap();
338 g.add_edge(1, 2).unwrap();
339 g.add_edge(0, 2).unwrap();
340 assert_eq!(eulerian_path(&g).unwrap(), None);
341 }
342
343 #[test]
344 fn complex_eulerian_path_test_eulerian_r() {
345 let mut g = Graph::with_vertices(6);
348 for &(u, v) in &[
349 (0, 1),
350 (1, 2),
351 (2, 3),
352 (3, 4),
353 (4, 0),
354 (0, 5),
355 (5, 3),
356 (3, 1),
357 (1, 5),
358 (5, 4),
359 ] {
360 g.add_edge(u, v).unwrap();
361 }
362 let walk = eulerian_path(&g).unwrap().unwrap();
363 assert_eq!(walk.len(), 10, "must visit every edge exactly once");
364 assert!(walk_validates(&g, &walk));
365 }
366}