Skip to main content

rust_igraph/algorithms/
dominating_set.rs

1//! Dominating set algorithms (ALGO-VC-003).
2//!
3//! A dominating set is a subset D of vertices such that every vertex
4//! is either in D or adjacent to at least one vertex in D. Finding a
5//! minimum dominating set is NP-hard; we provide a greedy
6//! O(ln n)-approximation.
7//!
8//! The greedy algorithm repeatedly picks the vertex that dominates
9//! the most currently-undominated vertices (including itself).
10
11use crate::core::{Graph, IgraphResult, VertexId};
12
13/// Check whether a set of vertices forms a valid dominating set.
14///
15/// A dominating set is valid if every vertex in the graph is either
16/// in the set or adjacent to at least one vertex in the set.
17///
18/// For directed graphs, a vertex v is considered dominated by a
19/// vertex u in the set if there is an edge in either direction
20/// between u and v.
21///
22/// # Examples
23///
24/// ```
25/// use rust_igraph::{Graph, is_dominating_set};
26///
27/// let mut g = Graph::with_vertices(5);
28/// g.add_edge(0, 1).unwrap();
29/// g.add_edge(1, 2).unwrap();
30/// g.add_edge(2, 3).unwrap();
31/// g.add_edge(3, 4).unwrap();
32///
33/// assert!(is_dominating_set(&g, &[1, 3]));
34/// assert!(!is_dominating_set(&g, &[0, 4]));
35/// ```
36pub fn is_dominating_set(graph: &Graph, dom_set: &[VertexId]) -> bool {
37    let n = graph.vcount() as usize;
38    if n == 0 {
39        return true;
40    }
41
42    let mut dominated = vec![false; n];
43    let directed = graph.is_directed();
44
45    for &v in dom_set {
46        if (v as usize) >= n {
47            continue;
48        }
49        dominated[v as usize] = true;
50
51        if let Ok(neighbors) = graph.neighbors(v) {
52            for &w in &neighbors {
53                dominated[w as usize] = true;
54            }
55        }
56        if directed {
57            if let Ok(in_neighbors) = graph.in_neighbors_vec(v) {
58                for &w in &in_neighbors {
59                    dominated[w as usize] = true;
60                }
61            }
62        }
63    }
64
65    dominated.iter().all(|&d| d)
66}
67
68/// Compute an approximate minimum dominating set using a greedy
69/// heuristic.
70///
71/// Repeatedly picks the vertex that dominates the most
72/// currently-undominated vertices (a vertex dominates itself and
73/// all its neighbors). This greedy approach yields an
74/// O(ln n)-approximation to the optimal minimum dominating set.
75///
76/// For directed graphs, adjacency considers edges in both
77/// directions.
78///
79/// # Examples
80///
81/// ```
82/// use rust_igraph::{Graph, minimum_dominating_set, is_dominating_set};
83///
84/// // Path 0-1-2-3-4: {1, 3} dominates all
85/// let mut g = Graph::with_vertices(5);
86/// g.add_edge(0, 1).unwrap();
87/// g.add_edge(1, 2).unwrap();
88/// g.add_edge(2, 3).unwrap();
89/// g.add_edge(3, 4).unwrap();
90/// let ds = minimum_dominating_set(&g).unwrap();
91/// assert!(is_dominating_set(&g, &ds));
92/// assert!(ds.len() <= 4); // greedy should do much better
93///
94/// // Isolated vertices: every vertex must be in the set
95/// let g = Graph::with_vertices(3);
96/// let ds = minimum_dominating_set(&g).unwrap();
97/// assert_eq!(ds.len(), 3);
98/// ```
99#[allow(clippy::cast_possible_truncation)] // vcount ≤ u32::MAX by graph invariant
100pub fn minimum_dominating_set(graph: &Graph) -> IgraphResult<Vec<VertexId>> {
101    let n = graph.vcount();
102    let directed = graph.is_directed();
103
104    if n == 0 {
105        return Ok(Vec::new());
106    }
107
108    let mut dominated = vec![false; n as usize];
109    let mut undominated_count = n;
110    let mut result = Vec::new();
111
112    // Precompute closed neighborhoods (vertex + all neighbors)
113    let mut closed_neighborhoods: Vec<Vec<VertexId>> = Vec::with_capacity(n as usize);
114    for v in 0..n {
115        let mut cn = vec![v];
116        let out_n = graph.neighbors(v)?;
117        for &w in &out_n {
118            if !cn.contains(&w) {
119                cn.push(w);
120            }
121        }
122        if directed {
123            let in_n = graph.in_neighbors_vec(v)?;
124            for &w in &in_n {
125                if !cn.contains(&w) {
126                    cn.push(w);
127                }
128            }
129        }
130        closed_neighborhoods.push(cn);
131    }
132
133    while undominated_count > 0 {
134        let mut best_v = 0u32;
135        let mut best_gain = 0u32;
136
137        for v in 0..n {
138            if dominated[v as usize] {
139                // Already dominated, but could still dominate others
140            }
141            let gain = closed_neighborhoods[v as usize]
142                .iter()
143                .filter(|&&w| !dominated[w as usize])
144                .count() as u32;
145            if gain > best_gain || (gain == best_gain && v < best_v) {
146                best_gain = gain;
147                best_v = v;
148            }
149        }
150
151        if best_gain == 0 {
152            break;
153        }
154
155        result.push(best_v);
156        for &w in &closed_neighborhoods[best_v as usize] {
157            if !dominated[w as usize] {
158                dominated[w as usize] = true;
159                undominated_count -= 1;
160            }
161        }
162    }
163
164    result.sort_unstable();
165    Ok(result)
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn empty_graph() {
174        let g = Graph::with_vertices(0);
175        let ds = minimum_dominating_set(&g).unwrap();
176        assert!(ds.is_empty());
177        assert!(is_dominating_set(&g, &ds));
178    }
179
180    #[test]
181    fn single_vertex() {
182        let g = Graph::with_vertices(1);
183        let ds = minimum_dominating_set(&g).unwrap();
184        assert_eq!(ds.len(), 1);
185        assert!(is_dominating_set(&g, &ds));
186    }
187
188    #[test]
189    fn no_edges() {
190        let g = Graph::with_vertices(5);
191        let ds = minimum_dominating_set(&g).unwrap();
192        assert_eq!(ds.len(), 5);
193        assert!(is_dominating_set(&g, &ds));
194    }
195
196    #[test]
197    fn single_edge() {
198        let mut g = Graph::with_vertices(2);
199        g.add_edge(0, 1).unwrap();
200        let ds = minimum_dominating_set(&g).unwrap();
201        assert_eq!(ds.len(), 1);
202        assert!(is_dominating_set(&g, &ds));
203    }
204
205    #[test]
206    fn path_5() {
207        let mut g = Graph::with_vertices(5);
208        g.add_edge(0, 1).unwrap();
209        g.add_edge(1, 2).unwrap();
210        g.add_edge(2, 3).unwrap();
211        g.add_edge(3, 4).unwrap();
212        let ds = minimum_dominating_set(&g).unwrap();
213        assert!(is_dominating_set(&g, &ds));
214        assert!(ds.len() <= 3); // optimal is 2
215    }
216
217    #[test]
218    fn triangle() {
219        let mut g = Graph::with_vertices(3);
220        g.add_edge(0, 1).unwrap();
221        g.add_edge(1, 2).unwrap();
222        g.add_edge(2, 0).unwrap();
223        let ds = minimum_dominating_set(&g).unwrap();
224        assert!(is_dominating_set(&g, &ds));
225        assert_eq!(ds.len(), 1);
226    }
227
228    #[test]
229    fn star_graph() {
230        let mut g = Graph::with_vertices(5);
231        for i in 1..5u32 {
232            g.add_edge(0, i).unwrap();
233        }
234        let ds = minimum_dominating_set(&g).unwrap();
235        assert!(is_dominating_set(&g, &ds));
236        assert_eq!(ds.len(), 1); // center dominates all
237    }
238
239    #[test]
240    fn complete_k4() {
241        let mut g = Graph::with_vertices(4);
242        for u in 0..4u32 {
243            for v in (u + 1)..4 {
244                g.add_edge(u, v).unwrap();
245            }
246        }
247        let ds = minimum_dominating_set(&g).unwrap();
248        assert!(is_dominating_set(&g, &ds));
249        assert_eq!(ds.len(), 1);
250    }
251
252    #[test]
253    fn bipartite_k22() {
254        let mut g = Graph::with_vertices(4);
255        g.add_edge(0, 2).unwrap();
256        g.add_edge(0, 3).unwrap();
257        g.add_edge(1, 2).unwrap();
258        g.add_edge(1, 3).unwrap();
259        let ds = minimum_dominating_set(&g).unwrap();
260        assert!(is_dominating_set(&g, &ds));
261        assert!(ds.len() <= 2);
262    }
263
264    #[test]
265    fn directed_graph() {
266        let mut g = Graph::new(3, true).unwrap();
267        g.add_edge(0, 1).unwrap();
268        g.add_edge(1, 2).unwrap();
269        let ds = minimum_dominating_set(&g).unwrap();
270        assert!(is_dominating_set(&g, &ds));
271    }
272
273    #[test]
274    fn self_loop() {
275        let mut g = Graph::with_vertices(3);
276        g.add_edge(0, 0).unwrap();
277        g.add_edge(1, 2).unwrap();
278        let ds = minimum_dominating_set(&g).unwrap();
279        assert!(is_dominating_set(&g, &ds));
280    }
281
282    #[test]
283    fn isolated_with_edges() {
284        let mut g = Graph::with_vertices(5);
285        g.add_edge(0, 1).unwrap();
286        g.add_edge(2, 3).unwrap();
287        // vertex 4 is isolated
288        let ds = minimum_dominating_set(&g).unwrap();
289        assert!(is_dominating_set(&g, &ds));
290        assert!(ds.contains(&4)); // isolated must be in DS
291    }
292
293    #[test]
294    fn sorted_output() {
295        let mut g = Graph::with_vertices(6);
296        g.add_edge(4, 5).unwrap();
297        g.add_edge(0, 1).unwrap();
298        g.add_edge(2, 3).unwrap();
299        let ds = minimum_dominating_set(&g).unwrap();
300        for i in 1..ds.len() {
301            assert!(ds[i] > ds[i - 1]);
302        }
303    }
304
305    #[test]
306    fn validator_positive() {
307        let mut g = Graph::with_vertices(5);
308        g.add_edge(0, 1).unwrap();
309        g.add_edge(1, 2).unwrap();
310        g.add_edge(2, 3).unwrap();
311        g.add_edge(3, 4).unwrap();
312        assert!(is_dominating_set(&g, &[1, 3]));
313        assert!(is_dominating_set(&g, &[0, 2, 4]));
314        assert!(is_dominating_set(&g, &[0, 1, 2, 3, 4]));
315    }
316
317    #[test]
318    fn validator_negative() {
319        let mut g = Graph::with_vertices(5);
320        g.add_edge(0, 1).unwrap();
321        g.add_edge(1, 2).unwrap();
322        g.add_edge(2, 3).unwrap();
323        g.add_edge(3, 4).unwrap();
324        assert!(!is_dominating_set(&g, &[]));
325        assert!(!is_dominating_set(&g, &[0, 4])); // vertex 2 not dominated
326        assert!(!is_dominating_set(&g, &[2])); // vertices 0, 4 not dominated
327    }
328
329    #[test]
330    fn petersen_graph() {
331        let mut g = Graph::with_vertices(10);
332        for i in 0..5u32 {
333            g.add_edge(i, (i + 1) % 5).unwrap();
334        }
335        for i in 0..5u32 {
336            g.add_edge(5 + i, 5 + (i + 2) % 5).unwrap();
337        }
338        for i in 0..5u32 {
339            g.add_edge(i, 5 + i).unwrap();
340        }
341        let ds = minimum_dominating_set(&g).unwrap();
342        assert!(is_dominating_set(&g, &ds));
343        // Petersen domination number is 3
344        assert!(ds.len() <= 5); // greedy should get ≤ 5
345    }
346
347    #[test]
348    fn directed_domination_both_directions() {
349        // 0→1, 2→1: vertex 1 is dominated by both 0 and 2 (via in-edges)
350        // {1} should dominate all since 0 is in-neighbor of 1, 2 is in-neighbor of 1
351        let mut g = Graph::new(3, true).unwrap();
352        g.add_edge(0, 1).unwrap();
353        g.add_edge(2, 1).unwrap();
354        assert!(is_dominating_set(&g, &[1]));
355    }
356}