Skip to main content

rust_igraph/algorithms/
independent_set.rs

1//! Independent set algorithms (ALGO-VC-002).
2//!
3//! An independent set is a set of vertices such that no two vertices
4//! in the set are adjacent. Finding a maximum independent set is
5//! NP-hard; we provide a greedy heuristic.
6//!
7//! The greedy algorithm repeatedly picks the vertex with minimum
8//! degree (among remaining vertices), adds it to the independent set,
9//! and removes it and all its neighbors from the candidate pool.
10
11use crate::core::{Graph, IgraphResult, VertexId};
12
13/// Compute an approximate maximum independent set using a greedy
14/// heuristic.
15///
16/// Repeatedly picks the vertex with the smallest degree among
17/// remaining candidates, adds it to the independent set, then
18/// removes it and all its neighbors from the candidate pool.
19///
20/// This is a simple greedy heuristic — it does not guarantee an
21/// optimal solution, but tends to produce good results in practice.
22///
23/// # Examples
24///
25/// ```
26/// use rust_igraph::{Graph, maximum_independent_set, is_independent_vertex_set};
27///
28/// // Path 0-1-2-3: maximum independent set is {0, 2} or {1, 3}
29/// let mut g = Graph::with_vertices(4);
30/// g.add_edge(0, 1).unwrap();
31/// g.add_edge(1, 2).unwrap();
32/// g.add_edge(2, 3).unwrap();
33/// let mis = maximum_independent_set(&g).unwrap();
34/// assert!(is_independent_vertex_set(&g, &mis).unwrap());
35/// assert!(mis.len() >= 2);
36///
37/// // Empty graph: all vertices are independent
38/// let g = Graph::with_vertices(5);
39/// let mis = maximum_independent_set(&g).unwrap();
40/// assert_eq!(mis.len(), 5);
41/// ```
42#[allow(clippy::cast_possible_truncation)] // vcount/ecount ≤ u32::MAX by graph invariant
43pub fn maximum_independent_set(graph: &Graph) -> IgraphResult<Vec<VertexId>> {
44    let n = graph.vcount();
45    let directed = graph.is_directed();
46
47    if n == 0 {
48        return Ok(Vec::new());
49    }
50
51    if graph.ecount() == 0 {
52        return Ok((0..n).collect());
53    }
54
55    let mut removed = vec![false; n as usize];
56
57    // Pre-exclude self-loop vertices (they can never be in an IS)
58    for v in 0..n {
59        let neighbors = all_neighbors(graph, v, directed)?;
60        if neighbors.contains(&v) {
61            removed[v as usize] = true;
62        }
63    }
64
65    let mut result = Vec::new();
66    let mut degrees = vec![0u32; n as usize];
67
68    for v in 0..n {
69        if !removed[v as usize] {
70            degrees[v as usize] = effective_degree(graph, v, &removed, directed)?;
71        }
72    }
73
74    loop {
75        let mut best_v: Option<VertexId> = None;
76        let mut best_deg = u32::MAX;
77
78        for v in 0..n {
79            if removed[v as usize] {
80                continue;
81            }
82            let deg = degrees[v as usize];
83            if deg < best_deg || (deg == best_deg && best_v.is_none_or(|b| v < b)) {
84                best_deg = deg;
85                best_v = Some(v);
86            }
87        }
88
89        let Some(v) = best_v else { break };
90
91        result.push(v);
92        removed[v as usize] = true;
93
94        let neighbors = all_neighbors(graph, v, directed)?;
95        for &w in &neighbors {
96            if !removed[w as usize] {
97                removed[w as usize] = true;
98            }
99        }
100
101        for u in 0..n {
102            if !removed[u as usize] {
103                degrees[u as usize] = effective_degree(graph, u, &removed, directed)?;
104            }
105        }
106    }
107
108    result.sort_unstable();
109    Ok(result)
110}
111
112fn all_neighbors(graph: &Graph, v: VertexId, directed: bool) -> IgraphResult<Vec<VertexId>> {
113    let mut neighbors = graph.neighbors(v)?;
114    if directed {
115        let in_n = graph.in_neighbors_vec(v)?;
116        for w in in_n {
117            if !neighbors.contains(&w) {
118                neighbors.push(w);
119            }
120        }
121    }
122    Ok(neighbors)
123}
124
125fn effective_degree(
126    graph: &Graph,
127    v: VertexId,
128    removed: &[bool],
129    directed: bool,
130) -> IgraphResult<u32> {
131    let neighbors = all_neighbors(graph, v, directed)?;
132    #[allow(clippy::cast_possible_truncation)]
133    let deg = neighbors
134        .iter()
135        .filter(|&&w| w != v && !removed[w as usize])
136        .count() as u32;
137    Ok(deg)
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::algorithms::properties::is_clique::is_independent_vertex_set;
144
145    #[test]
146    fn empty_graph() {
147        let g = Graph::with_vertices(0);
148        let mis = maximum_independent_set(&g).unwrap();
149        assert!(mis.is_empty());
150    }
151
152    #[test]
153    fn no_edges() {
154        let g = Graph::with_vertices(5);
155        let mis = maximum_independent_set(&g).unwrap();
156        assert_eq!(mis.len(), 5);
157        assert!(is_independent_vertex_set(&g, &mis).unwrap());
158    }
159
160    #[test]
161    fn single_edge() {
162        let mut g = Graph::with_vertices(2);
163        g.add_edge(0, 1).unwrap();
164        let mis = maximum_independent_set(&g).unwrap();
165        assert_eq!(mis.len(), 1);
166        assert!(is_independent_vertex_set(&g, &mis).unwrap());
167    }
168
169    #[test]
170    fn path_4() {
171        let mut g = Graph::with_vertices(4);
172        g.add_edge(0, 1).unwrap();
173        g.add_edge(1, 2).unwrap();
174        g.add_edge(2, 3).unwrap();
175        let mis = maximum_independent_set(&g).unwrap();
176        assert!(is_independent_vertex_set(&g, &mis).unwrap());
177        assert!(mis.len() >= 2); // optimal is 2
178    }
179
180    #[test]
181    fn triangle() {
182        let mut g = Graph::with_vertices(3);
183        g.add_edge(0, 1).unwrap();
184        g.add_edge(1, 2).unwrap();
185        g.add_edge(2, 0).unwrap();
186        let mis = maximum_independent_set(&g).unwrap();
187        assert!(is_independent_vertex_set(&g, &mis).unwrap());
188        assert_eq!(mis.len(), 1); // triangle MIS is 1
189    }
190
191    #[test]
192    fn star_graph() {
193        let mut g = Graph::with_vertices(5);
194        for i in 1..5u32 {
195            g.add_edge(0, i).unwrap();
196        }
197        let mis = maximum_independent_set(&g).unwrap();
198        assert!(is_independent_vertex_set(&g, &mis).unwrap());
199        // Optimal: {1,2,3,4} (leaves) = 4; greedy picks min-degree leaf first
200        assert!(mis.len() >= 2);
201    }
202
203    #[test]
204    fn complete_k4() {
205        let mut g = Graph::with_vertices(4);
206        for u in 0..4u32 {
207            for v in (u + 1)..4 {
208                g.add_edge(u, v).unwrap();
209            }
210        }
211        let mis = maximum_independent_set(&g).unwrap();
212        assert!(is_independent_vertex_set(&g, &mis).unwrap());
213        assert_eq!(mis.len(), 1); // K4 MIS is 1
214    }
215
216    #[test]
217    fn bipartite_k22() {
218        let mut g = Graph::with_vertices(4);
219        g.add_edge(0, 2).unwrap();
220        g.add_edge(0, 3).unwrap();
221        g.add_edge(1, 2).unwrap();
222        g.add_edge(1, 3).unwrap();
223        let mis = maximum_independent_set(&g).unwrap();
224        assert!(is_independent_vertex_set(&g, &mis).unwrap());
225        assert_eq!(mis.len(), 2); // K_{2,2} MIS is 2
226    }
227
228    #[test]
229    fn isolated_with_edges() {
230        let mut g = Graph::with_vertices(5);
231        g.add_edge(0, 1).unwrap();
232        g.add_edge(2, 3).unwrap();
233        // vertex 4 is isolated
234        let mis = maximum_independent_set(&g).unwrap();
235        assert!(is_independent_vertex_set(&g, &mis).unwrap());
236        assert!(mis.contains(&4)); // isolated vertex always in MIS
237        assert!(mis.len() >= 3); // at least one from each component + isolated
238    }
239
240    #[test]
241    fn directed_graph() {
242        let mut g = Graph::new(3, true).unwrap();
243        g.add_edge(0, 1).unwrap();
244        g.add_edge(1, 2).unwrap();
245        let mis = maximum_independent_set(&g).unwrap();
246        assert!(is_independent_vertex_set(&g, &mis).unwrap());
247    }
248
249    #[test]
250    fn self_loop() {
251        let mut g = Graph::with_vertices(3);
252        g.add_edge(0, 0).unwrap();
253        g.add_edge(1, 2).unwrap();
254        let mis = maximum_independent_set(&g).unwrap();
255        assert!(is_independent_vertex_set(&g, &mis).unwrap());
256        assert!(!mis.contains(&0)); // self-loop vertex can't be in IS
257    }
258
259    #[test]
260    fn sorted_output() {
261        let mut g = Graph::with_vertices(6);
262        g.add_edge(4, 5).unwrap();
263        g.add_edge(0, 1).unwrap();
264        let mis = maximum_independent_set(&g).unwrap();
265        for i in 1..mis.len() {
266            assert!(mis[i] > mis[i - 1]);
267        }
268    }
269
270    #[test]
271    fn petersen_graph() {
272        // Petersen graph: 10 vertices, MIS = 4
273        let mut g = Graph::with_vertices(10);
274        // Outer cycle
275        for i in 0..5u32 {
276            g.add_edge(i, (i + 1) % 5).unwrap();
277        }
278        // Inner pentagram
279        for i in 0..5u32 {
280            g.add_edge(5 + i, 5 + (i + 2) % 5).unwrap();
281        }
282        // Spokes
283        for i in 0..5u32 {
284            g.add_edge(i, 5 + i).unwrap();
285        }
286        let mis = maximum_independent_set(&g).unwrap();
287        assert!(is_independent_vertex_set(&g, &mis).unwrap());
288        assert!(mis.len() >= 3); // optimal is 4, greedy should get at least 3
289    }
290
291    #[test]
292    fn complement_of_vertex_cover() {
293        // For any graph: V \ vertex_cover is an independent set
294        use crate::algorithms::vertex_cover::minimum_vertex_cover;
295        let mut g = Graph::with_vertices(6);
296        for i in 0..5u32 {
297            g.add_edge(i, i + 1).unwrap();
298        }
299        let cover = minimum_vertex_cover(&g).unwrap();
300        let n = g.vcount();
301        let complement: Vec<VertexId> = (0..n).filter(|v| !cover.contains(v)).collect();
302        assert!(is_independent_vertex_set(&g, &complement).unwrap());
303    }
304}