rust_igraph/algorithms/
edge_cover.rs1use crate::core::{Graph, IgraphError, IgraphResult};
13
14type EdgeId = u32;
16
17#[allow(clippy::cast_possible_truncation)]
36pub fn is_edge_cover(graph: &Graph, cover: &[EdgeId]) -> bool {
37 let n = graph.vcount() as usize;
38 if n == 0 {
39 return true;
40 }
41
42 let mut covered = vec![false; n];
43
44 for &eid in cover {
45 if let Ok((u, v)) = graph.edge(eid) {
46 covered[u as usize] = true;
47 covered[v as usize] = true;
48 }
49 }
50
51 covered.iter().all(|&c| c)
52}
53
54#[allow(clippy::cast_possible_truncation)]
93pub fn minimum_edge_cover(graph: &Graph) -> IgraphResult<Vec<EdgeId>> {
94 let n = graph.vcount();
95
96 if n == 0 {
97 return Ok(Vec::new());
98 }
99
100 let directed = graph.is_directed();
101
102 for v in 0..n {
104 let out_edges = graph.incident(v)?;
105 let has_edges = if directed {
106 let in_edges = graph.incident_in(v)?;
107 !out_edges.is_empty() || !in_edges.is_empty()
108 } else {
109 !out_edges.is_empty()
110 };
111 if !has_edges {
112 return Err(IgraphError::InvalidArgument(format!(
113 "vertex {v} has degree 0; no edge cover exists"
114 )));
115 }
116 }
117
118 let ecount = graph.ecount();
119 if ecount == 0 {
120 return Err(IgraphError::InvalidArgument(
121 "graph has vertices but no edges; no edge cover exists".into(),
122 ));
123 }
124
125 let mut matched = vec![false; n as usize];
127 let mut cover = Vec::new();
128
129 for eid in 0..ecount as u32 {
130 let (u, v) = graph.edge(eid)?;
131 if !matched[u as usize] && !matched[v as usize] && u != v {
132 matched[u as usize] = true;
133 matched[v as usize] = true;
134 cover.push(eid);
135 }
136 }
137
138 for v in 0..n {
140 if !matched[v as usize] {
141 let mut edges = graph.incident(v)?;
142 if directed && edges.is_empty() {
143 edges = graph.incident_in(v)?;
144 }
145 if let Some(&eid) = edges.first() {
146 if !cover.contains(&eid) {
147 cover.push(eid);
148 }
149 matched[v as usize] = true;
150 }
151 }
152 }
153
154 cover.sort_unstable();
155 cover.dedup();
156 Ok(cover)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn empty_graph() {
165 let g = Graph::with_vertices(0);
166 let cover = minimum_edge_cover(&g).unwrap();
167 assert!(cover.is_empty());
168 }
169
170 #[test]
171 fn isolated_vertex_error() {
172 let g = Graph::with_vertices(3);
173 assert!(minimum_edge_cover(&g).is_err());
174 }
175
176 #[test]
177 fn single_edge() {
178 let mut g = Graph::with_vertices(2);
179 g.add_edge(0, 1).unwrap();
180 let cover = minimum_edge_cover(&g).unwrap();
181 assert_eq!(cover.len(), 1);
182 assert!(is_edge_cover(&g, &cover));
183 }
184
185 #[test]
186 fn path_3() {
187 let mut g = Graph::with_vertices(3);
188 g.add_edge(0, 1).unwrap();
189 g.add_edge(1, 2).unwrap();
190 let cover = minimum_edge_cover(&g).unwrap();
191 assert!(is_edge_cover(&g, &cover));
192 assert_eq!(cover.len(), 2);
193 }
194
195 #[test]
196 fn triangle() {
197 let mut g = Graph::with_vertices(3);
198 g.add_edge(0, 1).unwrap();
199 g.add_edge(1, 2).unwrap();
200 g.add_edge(2, 0).unwrap();
201 let cover = minimum_edge_cover(&g).unwrap();
202 assert!(is_edge_cover(&g, &cover));
203 assert_eq!(cover.len(), 2);
204 }
205
206 #[test]
207 fn star_graph() {
208 let mut g = Graph::with_vertices(5);
209 for i in 1..5u32 {
210 g.add_edge(0, i).unwrap();
211 }
212 let cover = minimum_edge_cover(&g).unwrap();
213 assert!(is_edge_cover(&g, &cover));
214 assert_eq!(cover.len(), 4);
221 }
222
223 #[test]
224 fn complete_k4() {
225 let mut g = Graph::with_vertices(4);
226 for u in 0..4u32 {
227 for v in (u + 1)..4 {
228 g.add_edge(u, v).unwrap();
229 }
230 }
231 let cover = minimum_edge_cover(&g).unwrap();
232 assert!(is_edge_cover(&g, &cover));
233 assert_eq!(cover.len(), 2);
235 }
236
237 #[test]
238 fn bipartite_k22() {
239 let mut g = Graph::with_vertices(4);
240 g.add_edge(0, 2).unwrap();
241 g.add_edge(0, 3).unwrap();
242 g.add_edge(1, 2).unwrap();
243 g.add_edge(1, 3).unwrap();
244 let cover = minimum_edge_cover(&g).unwrap();
245 assert!(is_edge_cover(&g, &cover));
246 assert_eq!(cover.len(), 2);
248 }
249
250 #[test]
251 fn path_5() {
252 let mut g = Graph::with_vertices(5);
253 g.add_edge(0, 1).unwrap();
254 g.add_edge(1, 2).unwrap();
255 g.add_edge(2, 3).unwrap();
256 g.add_edge(3, 4).unwrap();
257 let cover = minimum_edge_cover(&g).unwrap();
258 assert!(is_edge_cover(&g, &cover));
259 assert_eq!(cover.len(), 3);
261 }
262
263 #[test]
264 fn directed_graph() {
265 let mut g = Graph::new(3, true).unwrap();
266 g.add_edge(0, 1).unwrap();
267 g.add_edge(1, 2).unwrap();
268 let cover = minimum_edge_cover(&g).unwrap();
269 assert!(is_edge_cover(&g, &cover));
270 }
271
272 #[test]
273 fn self_loop_only() {
274 let mut g = Graph::with_vertices(1);
275 g.add_edge(0, 0).unwrap();
276 let cover = minimum_edge_cover(&g).unwrap();
277 assert!(is_edge_cover(&g, &cover));
278 assert_eq!(cover.len(), 1);
279 }
280
281 #[test]
282 fn mixed_isolated_error() {
283 let mut g = Graph::with_vertices(3);
284 g.add_edge(0, 1).unwrap();
285 assert!(minimum_edge_cover(&g).is_err());
287 }
288
289 #[test]
290 fn parallel_edges() {
291 let mut g = Graph::with_vertices(2);
292 g.add_edge(0, 1).unwrap();
293 g.add_edge(0, 1).unwrap();
294 let cover = minimum_edge_cover(&g).unwrap();
295 assert!(is_edge_cover(&g, &cover));
296 assert_eq!(cover.len(), 1);
297 }
298
299 #[test]
300 fn sorted_output() {
301 let mut g = Graph::with_vertices(4);
302 g.add_edge(2, 3).unwrap();
303 g.add_edge(0, 1).unwrap();
304 let cover = minimum_edge_cover(&g).unwrap();
305 for i in 1..cover.len() {
306 assert!(cover[i] > cover[i - 1]);
307 }
308 }
309
310 #[test]
311 fn validator_positive() {
312 let mut g = Graph::with_vertices(4);
313 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(2, 3).unwrap(); assert!(is_edge_cover(&g, &[0, 2]));
317 assert!(is_edge_cover(&g, &[0, 1, 2]));
318 }
319
320 #[test]
321 fn validator_negative() {
322 let mut g = Graph::with_vertices(4);
323 g.add_edge(0, 1).unwrap();
324 g.add_edge(1, 2).unwrap();
325 g.add_edge(2, 3).unwrap();
326 assert!(!is_edge_cover(&g, &[]));
327 assert!(!is_edge_cover(&g, &[1])); }
329}