Skip to main content

weavatrix_graph/algo/
matching.rs

1use crate::IndexUndirectedGraphView;
2use crate::Vec;
3use alloc::collections::VecDeque;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct MaximumMatching<Node> {
7    pairs: Vec<(Node, Node)>,
8}
9
10impl<Node> MaximumMatching<Node> {
11    #[must_use]
12    pub fn pairs(&self) -> &[(Node, Node)] {
13        &self.pairs
14    }
15
16    #[must_use]
17    pub const fn len(&self) -> usize {
18        self.pairs.len()
19    }
20
21    #[must_use]
22    pub const fn is_empty(&self) -> bool {
23        self.pairs.is_empty()
24    }
25}
26
27/// Computes a maximum-cardinality matching in a general undirected graph.
28pub fn maximum_matching<G>(graph: &G) -> MaximumMatching<G::Node>
29where
30    G: IndexUndirectedGraphView,
31{
32    let (nodes, adjacency) = indexed(graph);
33    let matching = Blossom::new(adjacency).solve();
34    let pairs = matching
35        .iter()
36        .enumerate()
37        .filter_map(|(left, right)| {
38            let right = (*right)?;
39            (left < right).then(|| Some((nodes[left]?, nodes[right]?)))?
40        })
41        .collect();
42    MaximumMatching { pairs }
43}
44
45fn indexed<G: IndexUndirectedGraphView>(graph: &G) -> (Vec<Option<G::Node>>, Vec<Vec<usize>>) {
46    let mut nodes = vec![None; graph.node_bound()];
47    let mut adjacency = vec![Vec::new(); graph.node_bound()];
48    for node in graph.node_indices() {
49        let slot = G::node_slot(node);
50        nodes[slot] = Some(node);
51        adjacency[slot] = graph
52            .incident_edges(node)
53            .filter_map(|edge| graph.opposite(edge, node))
54            .map(G::node_slot)
55            .filter(|neighbor| *neighbor != slot)
56            .collect();
57    }
58    (nodes, adjacency)
59}
60
61struct Blossom {
62    adjacency: Vec<Vec<usize>>,
63    matching: Vec<Option<usize>>,
64    parent: Vec<Option<usize>>,
65    base: Vec<usize>,
66    used: Vec<bool>,
67    contracted: Vec<bool>,
68}
69
70impl Blossom {
71    fn new(adjacency: Vec<Vec<usize>>) -> Self {
72        let bound = adjacency.len();
73        Self {
74            adjacency,
75            matching: vec![None; bound],
76            parent: vec![None; bound],
77            base: (0..bound).collect(),
78            used: vec![false; bound],
79            contracted: vec![false; bound],
80        }
81    }
82
83    fn solve(mut self) -> Vec<Option<usize>> {
84        self.seed_greedy();
85        for root in 0..self.adjacency.len() {
86            if self.matching[root].is_none() {
87                self.find_augmenting_path(root);
88            }
89        }
90        self.matching
91    }
92
93    fn seed_greedy(&mut self) {
94        for left in 0..self.adjacency.len() {
95            if self.matching[left].is_some() {
96                continue;
97            }
98            let right = self.adjacency[left]
99                .iter()
100                .copied()
101                .find(|right| self.matching[*right].is_none());
102            if let Some(right) = right {
103                self.matching[left] = Some(right);
104                self.matching[right] = Some(left);
105            }
106        }
107    }
108
109    fn find_augmenting_path(&mut self, root: usize) -> bool {
110        self.used.fill(false);
111        self.parent.fill(None);
112        for (slot, base) in self.base.iter_mut().enumerate() {
113            *base = slot;
114        }
115        let mut queue = VecDeque::from([root]);
116        self.used[root] = true;
117        while let Some(node) = queue.pop_front() {
118            for index in 0..self.adjacency[node].len() {
119                let neighbor = self.adjacency[node][index];
120                if self.base[node] == self.base[neighbor] || self.matching[node] == Some(neighbor) {
121                    continue;
122                }
123                if neighbor == root
124                    || self.matching[neighbor]
125                        .and_then(|matched| self.parent[matched])
126                        .is_some()
127                {
128                    self.contract(node, neighbor, &mut queue);
129                } else if self.parent[neighbor].is_none() {
130                    self.parent[neighbor] = Some(node);
131                    if self.matching[neighbor].is_none() {
132                        self.augment(neighbor);
133                        return true;
134                    }
135                    if let Some(matched) = self.matching[neighbor] {
136                        self.used[matched] = true;
137                        queue.push_back(matched);
138                    }
139                }
140            }
141        }
142        false
143    }
144
145    fn contract(&mut self, left: usize, right: usize, queue: &mut VecDeque<usize>) {
146        let base = self.lowest_common_base(left, right);
147        self.contracted.fill(false);
148        self.mark_path(left, right, base);
149        self.mark_path(right, left, base);
150        for node in 0..self.adjacency.len() {
151            if self.contracted[self.base[node]] {
152                self.base[node] = base;
153                if !self.used[node] {
154                    self.used[node] = true;
155                    queue.push_back(node);
156                }
157            }
158        }
159    }
160
161    fn lowest_common_base(&self, mut left: usize, mut right: usize) -> usize {
162        let mut path = vec![false; self.adjacency.len()];
163        loop {
164            left = self.base[left];
165            path[left] = true;
166            let Some(matched) = self.matching[left] else {
167                break;
168            };
169            let Some(parent) = self.parent[matched] else {
170                break;
171            };
172            left = parent;
173        }
174        loop {
175            right = self.base[right];
176            if path[right] {
177                return right;
178            }
179            let Some(matched) = self.matching[right] else {
180                return right;
181            };
182            let Some(parent) = self.parent[matched] else {
183                return right;
184            };
185            right = parent;
186        }
187    }
188
189    fn mark_path(&mut self, mut node: usize, mut child: usize, base: usize) {
190        while self.base[node] != base {
191            let Some(matched) = self.matching[node] else {
192                break;
193            };
194            self.contracted[self.base[node]] = true;
195            self.contracted[self.base[matched]] = true;
196            self.parent[node] = Some(child);
197            child = matched;
198            let Some(parent) = self.parent[matched] else {
199                break;
200            };
201            node = parent;
202        }
203    }
204
205    fn augment(&mut self, mut node: usize) {
206        while let Some(parent) = self.parent[node] {
207            let next = self.matching[parent];
208            self.matching[node] = Some(parent);
209            self.matching[parent] = Some(node);
210            let Some(next) = next else {
211                break;
212            };
213            node = next;
214        }
215    }
216}