Skip to main content

rust_igraph/algorithms/
vertex_cover.rs

1//! Vertex cover algorithms (ALGO-VC-001).
2//!
3//! A vertex cover is a set of vertices such that every edge in the
4//! graph is incident to at least one vertex in the set. Finding a
5//! minimum vertex cover is NP-hard; we provide a greedy
6//! 2-approximation.
7//!
8//! The greedy algorithm repeatedly picks an uncovered edge and adds
9//! both endpoints to the cover. This guarantees the result is at most
10//! twice the size of the optimal cover (König 1931 / Gavril 1974).
11
12use crate::core::{Graph, IgraphResult, VertexId};
13
14/// Check whether a set of vertices forms a valid vertex cover.
15///
16/// A vertex cover is valid if every edge in the graph has at least
17/// one endpoint in the set.
18///
19/// # Examples
20///
21/// ```
22/// use rust_igraph::{Graph, is_vertex_cover};
23///
24/// let mut g = Graph::with_vertices(4);
25/// g.add_edge(0, 1).unwrap();
26/// g.add_edge(1, 2).unwrap();
27/// g.add_edge(2, 3).unwrap();
28///
29/// assert!(is_vertex_cover(&g, &[1, 2]));
30/// assert!(!is_vertex_cover(&g, &[0, 3]));
31/// ```
32pub fn is_vertex_cover(graph: &Graph, cover: &[VertexId]) -> bool {
33    let n = graph.vcount() as usize;
34    let mut in_cover = vec![false; n];
35    for &v in cover {
36        if (v as usize) < n {
37            in_cover[v as usize] = true;
38        }
39    }
40
41    let ecount = graph.ecount();
42    #[allow(clippy::cast_possible_truncation)] // ecount ≤ u32::MAX by graph invariant
43    for eid in 0..ecount as u32 {
44        if let Ok((u, v)) = graph.edge(eid) {
45            if !in_cover[u as usize] && !in_cover[v as usize] {
46                return false;
47            }
48        }
49    }
50    true
51}
52
53/// Compute a minimum vertex cover using a greedy 2-approximation.
54///
55/// Repeatedly picks an uncovered edge and adds both endpoints to
56/// the cover. The result is guaranteed to be at most twice the
57/// size of the optimal minimum vertex cover.
58///
59/// For undirected graphs, direction is ignored. For directed graphs,
60/// each arc counts as an edge that must be covered.
61///
62/// # Examples
63///
64/// ```
65/// use rust_igraph::{Graph, minimum_vertex_cover, is_vertex_cover};
66///
67/// // Path 0-1-2-3: optimal cover is {1, 2}, greedy gives at most 4
68/// let mut g = Graph::with_vertices(4);
69/// g.add_edge(0, 1).unwrap();
70/// g.add_edge(1, 2).unwrap();
71/// g.add_edge(2, 3).unwrap();
72/// let cover = minimum_vertex_cover(&g).unwrap();
73/// assert!(is_vertex_cover(&g, &cover));
74/// assert!(cover.len() <= 4); // 2-approx of opt=2
75///
76/// // Empty graph: no edges, empty cover
77/// let g = Graph::with_vertices(5);
78/// let cover = minimum_vertex_cover(&g).unwrap();
79/// assert!(cover.is_empty());
80/// ```
81#[allow(clippy::cast_possible_truncation)] // vcount/ecount ≤ u32::MAX by graph invariant
82pub fn minimum_vertex_cover(graph: &Graph) -> IgraphResult<Vec<VertexId>> {
83    let n = graph.vcount();
84    let ecount = graph.ecount();
85
86    if ecount == 0 {
87        return Ok(Vec::new());
88    }
89
90    let mut in_cover = vec![false; n as usize];
91    let mut covered = vec![false; ecount];
92
93    for eid in 0..ecount {
94        if covered[eid] {
95            continue;
96        }
97
98        let (u, v) = graph.edge(eid as u32)?;
99        if in_cover[u as usize] || in_cover[v as usize] {
100            covered[eid] = true;
101            continue;
102        }
103
104        in_cover[u as usize] = true;
105        in_cover[v as usize] = true;
106
107        mark_incident_covered(graph, u, &mut covered);
108        mark_incident_covered(graph, v, &mut covered);
109    }
110
111    let mut result: Vec<VertexId> = (0..n).filter(|&v| in_cover[v as usize]).collect();
112    result.sort_unstable();
113    Ok(result)
114}
115
116fn mark_incident_covered(graph: &Graph, v: VertexId, covered: &mut [bool]) {
117    if let Ok(edges) = graph.incident(v) {
118        for &eid in &edges {
119            covered[eid as usize] = true;
120        }
121    }
122    if graph.is_directed() {
123        if let Ok(edges) = graph.incident_in(v) {
124            for &eid in &edges {
125                covered[eid as usize] = true;
126            }
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn empty_graph() {
137        let g = Graph::with_vertices(0);
138        let cover = minimum_vertex_cover(&g).unwrap();
139        assert!(cover.is_empty());
140    }
141
142    #[test]
143    fn no_edges() {
144        let g = Graph::with_vertices(5);
145        let cover = minimum_vertex_cover(&g).unwrap();
146        assert!(cover.is_empty());
147    }
148
149    #[test]
150    fn single_edge() {
151        let mut g = Graph::with_vertices(2);
152        g.add_edge(0, 1).unwrap();
153        let cover = minimum_vertex_cover(&g).unwrap();
154        assert_eq!(cover.len(), 2);
155        assert!(is_vertex_cover(&g, &cover));
156    }
157
158    #[test]
159    fn path_3() {
160        let mut g = Graph::with_vertices(3);
161        g.add_edge(0, 1).unwrap();
162        g.add_edge(1, 2).unwrap();
163        let cover = minimum_vertex_cover(&g).unwrap();
164        assert!(is_vertex_cover(&g, &cover));
165        assert!(cover.len() <= 4); // 2-approx of opt=1
166    }
167
168    #[test]
169    fn triangle() {
170        let mut g = Graph::with_vertices(3);
171        g.add_edge(0, 1).unwrap();
172        g.add_edge(1, 2).unwrap();
173        g.add_edge(2, 0).unwrap();
174        let cover = minimum_vertex_cover(&g).unwrap();
175        assert!(is_vertex_cover(&g, &cover));
176        assert!(cover.len() >= 2); // min cover of triangle is 2
177        assert!(cover.len() <= 4); // 2-approx
178    }
179
180    #[test]
181    fn star_graph() {
182        let mut g = Graph::with_vertices(5);
183        for i in 1..5u32 {
184            g.add_edge(0, i).unwrap();
185        }
186        let cover = minimum_vertex_cover(&g).unwrap();
187        assert!(is_vertex_cover(&g, &cover));
188        // Optimal is {0} (size 1), 2-approx gives at most 2
189        assert!(cover.len() <= 2);
190    }
191
192    #[test]
193    fn complete_k4() {
194        let mut g = Graph::with_vertices(4);
195        for u in 0..4u32 {
196            for v in (u + 1)..4 {
197                g.add_edge(u, v).unwrap();
198            }
199        }
200        let cover = minimum_vertex_cover(&g).unwrap();
201        assert!(is_vertex_cover(&g, &cover));
202        assert!(cover.len() >= 3); // K4 minimum cover is 3
203    }
204
205    #[test]
206    fn bipartite_matching_example() {
207        // K_{2,2}: 0-2, 0-3, 1-2, 1-3
208        let mut g = Graph::with_vertices(4);
209        g.add_edge(0, 2).unwrap();
210        g.add_edge(0, 3).unwrap();
211        g.add_edge(1, 2).unwrap();
212        g.add_edge(1, 3).unwrap();
213        let cover = minimum_vertex_cover(&g).unwrap();
214        assert!(is_vertex_cover(&g, &cover));
215        assert!(cover.len() >= 2); // minimum cover is 2
216        assert!(cover.len() <= 4); // 2-approx
217    }
218
219    #[test]
220    fn directed_graph() {
221        let mut g = Graph::new(3, true).unwrap();
222        g.add_edge(0, 1).unwrap();
223        g.add_edge(1, 2).unwrap();
224        let cover = minimum_vertex_cover(&g).unwrap();
225        assert!(is_vertex_cover(&g, &cover));
226    }
227
228    #[test]
229    fn self_loop() {
230        let mut g = Graph::with_vertices(3);
231        g.add_edge(0, 0).unwrap();
232        g.add_edge(1, 2).unwrap();
233        let cover = minimum_vertex_cover(&g).unwrap();
234        assert!(is_vertex_cover(&g, &cover));
235        assert!(cover.contains(&0)); // self-loop needs vertex 0
236    }
237
238    #[test]
239    fn isolated_with_edges() {
240        let mut g = Graph::with_vertices(5);
241        g.add_edge(0, 1).unwrap();
242        g.add_edge(2, 3).unwrap();
243        // vertex 4 is isolated
244        let cover = minimum_vertex_cover(&g).unwrap();
245        assert!(is_vertex_cover(&g, &cover));
246        assert!(!cover.contains(&4));
247    }
248
249    #[test]
250    fn two_approx_guarantee() {
251        // Path of length 5: 0-1-2-3-4-5
252        // Optimal cover: {1, 3, 5} (size 3) or {1, 3} won't work...
253        // Actually opt is {1, 3, 5} or {0, 2, 4} — size 3
254        // Greedy picks first edge 0-1 → both in cover
255        // 2-approx → at most 6
256        let mut g = Graph::with_vertices(6);
257        for i in 0..5u32 {
258            g.add_edge(i, i + 1).unwrap();
259        }
260        let cover = minimum_vertex_cover(&g).unwrap();
261        assert!(is_vertex_cover(&g, &cover));
262        assert!(cover.len() <= 6); // 2 * opt(3) = 6
263    }
264
265    #[test]
266    fn is_cover_validator_positive() {
267        let mut g = Graph::with_vertices(3);
268        g.add_edge(0, 1).unwrap();
269        g.add_edge(1, 2).unwrap();
270        assert!(is_vertex_cover(&g, &[1]));
271        assert!(is_vertex_cover(&g, &[0, 2]));
272        assert!(is_vertex_cover(&g, &[0, 1, 2]));
273    }
274
275    #[test]
276    fn is_cover_validator_negative() {
277        let mut g = Graph::with_vertices(4);
278        g.add_edge(0, 1).unwrap();
279        g.add_edge(1, 2).unwrap();
280        g.add_edge(2, 3).unwrap();
281        assert!(!is_vertex_cover(&g, &[]));
282        assert!(!is_vertex_cover(&g, &[0]));
283        assert!(!is_vertex_cover(&g, &[0, 3]));
284    }
285
286    #[test]
287    fn sorted_output() {
288        let mut g = Graph::with_vertices(6);
289        g.add_edge(4, 5).unwrap();
290        g.add_edge(0, 1).unwrap();
291        g.add_edge(2, 3).unwrap();
292        let cover = minimum_vertex_cover(&g).unwrap();
293        for i in 1..cover.len() {
294            assert!(cover[i] > cover[i - 1]);
295        }
296    }
297
298    #[test]
299    fn parallel_edges() {
300        let mut g = Graph::with_vertices(2);
301        g.add_edge(0, 1).unwrap();
302        g.add_edge(0, 1).unwrap();
303        g.add_edge(0, 1).unwrap();
304        let cover = minimum_vertex_cover(&g).unwrap();
305        assert!(is_vertex_cover(&g, &cover));
306        assert_eq!(cover.len(), 2);
307    }
308}