Skip to main content

problemreductions/models/graph/
maximum_leaf_spanning_tree.rs

1//! Maximum Leaf Spanning Tree problem implementation.
2//!
3//! Given a connected graph G, find a spanning tree T of G that maximizes
4//! the number of leaves (degree-1 vertices) in T.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::Max;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13    ProblemSchemaEntry {
14        name: "MaximumLeafSpanningTree",
15        display_name: "Maximum Leaf Spanning Tree",
16        aliases: &[],
17        dimensions: &[
18            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
19        ],
20        module_path: module_path!(),
21        description: "Find spanning tree maximizing the number of leaves",
22        fields: &[
23            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
24        ],
25    }
26}
27
28/// The Maximum Leaf Spanning Tree problem.
29///
30/// Given a connected graph G = (V, E), find a spanning tree T of G such that
31/// the number of leaves (vertices with degree 1 in T) is maximized.
32///
33/// # Representation
34///
35/// Each edge is assigned a binary variable:
36/// - 0: edge is not in the spanning tree
37/// - 1: edge is in the spanning tree
38///
39/// A valid spanning tree requires exactly n-1 selected edges that form a
40/// connected, acyclic subgraph spanning all vertices.
41///
42/// # Type Parameters
43///
44/// * `G` - The graph type (e.g., `SimpleGraph`)
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct MaximumLeafSpanningTree<G> {
47    /// The underlying graph.
48    graph: G,
49}
50
51impl<G: Graph> MaximumLeafSpanningTree<G> {
52    /// Create a MaximumLeafSpanningTree problem from a graph.
53    ///
54    /// The graph must have at least 2 vertices.
55    pub fn new(graph: G) -> Self {
56        assert!(
57            graph.num_vertices() >= 2,
58            "graph must have at least 2 vertices"
59        );
60        Self { graph }
61    }
62
63    /// Get a reference to the underlying graph.
64    pub fn graph(&self) -> &G {
65        &self.graph
66    }
67
68    /// Get the number of vertices in the underlying graph.
69    pub fn num_vertices(&self) -> usize {
70        self.graph.num_vertices()
71    }
72
73    /// Get the number of edges in the underlying graph.
74    pub fn num_edges(&self) -> usize {
75        self.graph.num_edges()
76    }
77
78    /// Check if a configuration is a valid spanning tree.
79    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
80        is_valid_spanning_tree(&self.graph, config)
81    }
82}
83
84/// Check if a configuration forms a valid spanning tree:
85/// 1. Exactly n-1 edges selected
86/// 2. Selected edges form a connected subgraph (which, combined with n-1 edges, implies a tree)
87fn is_valid_spanning_tree<G: Graph>(graph: &G, config: &[usize]) -> bool {
88    let n = graph.num_vertices();
89    let edges = graph.edges();
90    if config.len() != edges.len() {
91        return false;
92    }
93
94    // Count selected edges
95    let selected_count: usize = config.iter().sum();
96    if selected_count != n - 1 {
97        return false;
98    }
99
100    // Build adjacency from selected edges and check connectivity via BFS
101    let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
102    for (idx, &sel) in config.iter().enumerate() {
103        if sel == 1 {
104            let (u, v) = edges[idx];
105            adj[u].push(v);
106            adj[v].push(u);
107        }
108    }
109
110    // BFS from vertex 0
111    let mut visited = vec![false; n];
112    let mut queue = std::collections::VecDeque::new();
113    visited[0] = true;
114    queue.push_back(0);
115    while let Some(v) = queue.pop_front() {
116        for &u in &adj[v] {
117            if !visited[u] {
118                visited[u] = true;
119                queue.push_back(u);
120            }
121        }
122    }
123
124    // All vertices must be reachable
125    visited.iter().all(|&v| v)
126}
127
128/// Count the number of leaves (degree-1 vertices) in the tree defined by the config.
129fn count_leaves<G: Graph>(graph: &G, config: &[usize]) -> usize {
130    let n = graph.num_vertices();
131    let edges = graph.edges();
132    let mut degree = vec![0usize; n];
133    for (idx, &sel) in config.iter().enumerate() {
134        if sel == 1 {
135            let (u, v) = edges[idx];
136            degree[u] += 1;
137            degree[v] += 1;
138        }
139    }
140    degree.iter().filter(|&&d| d == 1).count()
141}
142
143impl<G> Problem for MaximumLeafSpanningTree<G>
144where
145    G: Graph + crate::variant::VariantParam,
146{
147    const NAME: &'static str = "MaximumLeafSpanningTree";
148    type Value = Max<usize>;
149
150    fn variant() -> Vec<(&'static str, &'static str)> {
151        crate::variant_params![G]
152    }
153
154    fn dims(&self) -> Vec<usize> {
155        vec![2; self.graph.num_edges()]
156    }
157
158    fn evaluate(&self, config: &[usize]) -> Max<usize> {
159        if !is_valid_spanning_tree(&self.graph, config) {
160            return Max(None);
161        }
162        Max(Some(count_leaves(&self.graph, config)))
163    }
164}
165
166crate::declare_variants! {
167    default MaximumLeafSpanningTree<SimpleGraph> => "1.8966^num_vertices",
168}
169
170#[cfg(feature = "example-db")]
171pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
172    vec![crate::example_db::specs::ModelExampleSpec {
173        id: "maximum_leaf_spanning_tree_simplegraph",
174        instance: Box::new(MaximumLeafSpanningTree::new(SimpleGraph::new(
175            6,
176            vec![
177                (0, 1),
178                (0, 2),
179                (0, 3),
180                (1, 4),
181                (2, 4),
182                (2, 5),
183                (3, 5),
184                (4, 5),
185                (1, 3),
186            ],
187        ))),
188        // Edges: 0:(0,1), 1:(0,2), 2:(0,3), 3:(1,4), 4:(2,4), 5:(2,5), 6:(3,5), 7:(4,5), 8:(1,3)
189        // Tree: {(0,1),(0,2),(0,3),(2,4),(2,5)} = indices 0,1,2,4,5
190        // Leaves: 1,3,4,5 (degree 1 each), Internal: 0 (deg 3), 2 (deg 3)
191        optimal_config: vec![1, 1, 1, 0, 1, 1, 0, 0, 0],
192        optimal_value: serde_json::json!(4),
193    }]
194}
195
196#[cfg(test)]
197#[path = "../../unit_tests/models/graph/maximum_leaf_spanning_tree.rs"]
198mod tests;