algorithms_edu/algo/graph/
eulerian_path.rs1use crate::algo::graph::UnweightedAdjacencyList;
12
13#[derive(Debug)]
14pub enum EulerianPathError {
15 DisconnectedGraph,
16 InvalidDegrees,
17}
18
19impl UnweightedAdjacencyList {
20 fn count_in_out_degrees(&self) -> [Vec<usize>; 2] {
21 let mut in_degrees = vec![0; self.node_count()];
22 let mut out_degrees = vec![0; self.node_count()];
23 for [from, to] in self.edges() {
24 out_degrees[from] += 1;
25 in_degrees[to] += 1;
26 }
27 [in_degrees, out_degrees]
28 }
29 pub fn eulerian_path(&self) -> Result<Vec<usize>, EulerianPathError> {
32 let n = self.node_count();
33 let has_eulerian_path = |[in_degrees, out_degrees]: [&[usize]; 2]| {
34 let mut start = None;
35 let mut has_end = false;
36 let mut start_default = None;
37 for (node, (&i, &o)) in in_degrees.iter().zip(out_degrees.iter()).enumerate() {
38 if (i as isize - o as isize).abs() > 1 {
39 return None;
40 }
41 if i.wrapping_sub(o) == 1 {
42 if has_end {
43 return None;
44 } else {
45 has_end = true;
46 }
47 }
48 if o.wrapping_sub(i) == 1 {
49 if start.is_some() {
50 return None;
51 } else {
52 start = Some(node)
53 }
54 }
55 if start_default.is_none() && o > 0 {
56 start_default = Some(node);
57 }
58 }
59 if start.is_some() ^ has_end {
60 None
61 } else {
62 Some(start.unwrap_or_else(|| start_default.unwrap()))
63 }
64 };
65 let [i, mut o] = self.count_in_out_degrees();
66 fn _dfs(
67 g: &UnweightedAdjacencyList,
68 out: &mut Vec<usize>,
69 path: &mut Vec<usize>,
70 at: usize,
71 ) {
72 while out[at] > 0 {
74 out[at] -= 1;
75 let next = g[at][out[at]];
77 _dfs(g, out, path, next);
78 }
79 path.push(at);
81 };
82 has_eulerian_path([&i, &o]).map_or(Err(EulerianPathError::InvalidDegrees), |start| {
83 let mut path = Vec::with_capacity(n);
84 _dfs(self, &mut o, &mut path, start);
85 path.reverse();
86 if path.len() == self.edge_count() + 1 {
89 Ok(path)
90 } else {
91 Err(EulerianPathError::DisconnectedGraph)
93 }
94 })
95 }
96}
97#[cfg(test)]
98mod tests {
99
100 use super::*;
101 #[test]
102 fn test_eulerian_path() {
103 let g = UnweightedAdjacencyList::new_directed(
104 7,
105 &[
106 [1, 2],
107 [1, 3],
108 [2, 2],
109 [2, 4],
110 [2, 4],
111 [3, 1],
112 [3, 2],
113 [3, 5],
114 [4, 3],
115 [4, 6],
116 [5, 6],
117 [6, 3],
118 ],
119 );
120 let path = g.eulerian_path().unwrap();
121 assert_eq!(&path, &[1, 3, 5, 6, 3, 2, 4, 3, 1, 2, 2, 4, 6]);
122
123 let g = UnweightedAdjacencyList::new_directed(
124 5,
125 &[[0, 1], [1, 2], [1, 4], [1, 3], [2, 1], [4, 1]],
126 );
127 let path = g.eulerian_path().unwrap();
128 assert_eq!(&path, &[0, 1, 4, 1, 2, 1, 3]);
129 }
130 #[test]
131 fn test_eulerian_path_invalid1() {
132 let g = UnweightedAdjacencyList::new_directed(2, &[[0, 1], [0, 1]]);
133 assert!(matches!(
134 g.eulerian_path(),
135 Err(EulerianPathError::InvalidDegrees)
136 ));
137 }
138 #[test]
139 fn test_eulerian_path_invalid2() {
140 let g = UnweightedAdjacencyList::new_directed(3, &[[0, 1], [1, 0], [1, 2], [2, 0], [2, 0]]);
141 assert!(matches!(
142 g.eulerian_path(),
143 Err(EulerianPathError::InvalidDegrees)
144 ));
145 }
146 #[test]
147 fn test_eulerian_path_invalid3() {
148 let g = UnweightedAdjacencyList::new_directed(
149 4,
150 &[
151 [0, 2],
152 [2, 1],
153 [2, 3],
154 [3, 0],
155 [3, 1],
156 [1, 3],
157 [1, 0],
158 [1, 2],
159 [0, 3],
160 [2, 0],
161 ],
162 );
163 assert!(matches!(
164 g.eulerian_path(),
165 Err(EulerianPathError::InvalidDegrees)
166 ));
167 }
168
169 #[test]
170 fn test_eulerian_path_invalid4() {
171 let g = UnweightedAdjacencyList::new_directed(
172 8,
173 &[
174 [0, 1],
175 [1, 2],
176 [2, 3],
177 [3, 1],
178 [4, 5],
179 [5, 6],
180 [6, 7],
181 [7, 4],
182 ],
183 );
184 assert!(matches!(
185 g.eulerian_path(),
186 Err(EulerianPathError::DisconnectedGraph)
187 ));
188 }
189}