petgraph/algo/
matching.rs

1use alloc::{collections::VecDeque, vec, vec::Vec};
2use core::hash::Hash;
3
4use crate::visit::{
5    EdgeRef, GraphBase, IntoEdges, IntoNeighbors, IntoNodeIdentifiers, NodeCount, NodeIndexable,
6    VisitMap, Visitable,
7};
8
9/// Computed
10/// [*matching*](https://en.wikipedia.org/wiki/Matching_(graph_theory)#Definitions)
11/// of the graph.
12pub struct Matching<G: GraphBase> {
13    graph: G,
14    mate: Vec<Option<G::NodeId>>,
15    n_edges: usize,
16}
17
18impl<G> Matching<G>
19where
20    G: GraphBase,
21{
22    fn new(graph: G, mate: Vec<Option<G::NodeId>>, n_edges: usize) -> Self {
23        Self {
24            graph,
25            mate,
26            n_edges,
27        }
28    }
29}
30
31impl<G> Matching<G>
32where
33    G: NodeIndexable,
34{
35    /// Gets the matched counterpart of given node, if there is any.
36    ///
37    /// Returns `None` if the node is not matched or does not exist.
38    pub fn mate(&self, node: G::NodeId) -> Option<G::NodeId> {
39        self.mate.get(self.graph.to_index(node)).and_then(|&id| id)
40    }
41
42    /// Iterates over all edges from the matching.
43    ///
44    /// An edge is represented by its endpoints. The graph is considered
45    /// undirected and every pair of matched nodes is reported only once.
46    pub fn edges(&self) -> MatchedEdges<'_, G> {
47        MatchedEdges {
48            graph: &self.graph,
49            mate: self.mate.as_slice(),
50            current: 0,
51        }
52    }
53
54    /// Iterates over all nodes from the matching.
55    pub fn nodes(&self) -> MatchedNodes<'_, G> {
56        MatchedNodes {
57            graph: &self.graph,
58            mate: self.mate.as_slice(),
59            current: 0,
60        }
61    }
62
63    /// Returns `true` if given edge is in the matching, or `false` otherwise.
64    ///
65    /// If any of the nodes does not exist, `false` is returned.
66    pub fn contains_edge(&self, a: G::NodeId, b: G::NodeId) -> bool {
67        match self.mate(a) {
68            Some(mate) => mate == b,
69            None => false,
70        }
71    }
72
73    /// Returns `true` if given node is in the matching, or `false` otherwise.
74    ///
75    /// If the node does not exist, `false` is returned.
76    pub fn contains_node(&self, node: G::NodeId) -> bool {
77        self.mate(node).is_some()
78    }
79
80    /// Gets the number of matched **edges**.
81    pub fn len(&self) -> usize {
82        self.n_edges
83    }
84
85    /// Returns `true` if the number of matched **edges** is 0.
86    pub fn is_empty(&self) -> bool {
87        self.len() == 0
88    }
89}
90
91impl<G> Matching<G>
92where
93    G: NodeCount,
94{
95    /// Returns `true` if the matching is perfect.
96    ///
97    /// A matching is
98    /// [*perfect*](https://en.wikipedia.org/wiki/Matching_(graph_theory)#Definitions)
99    /// if every node in the graph is incident to an edge from the matching.
100    pub fn is_perfect(&self) -> bool {
101        let n_nodes = self.graph.node_count();
102        n_nodes % 2 == 0 && self.n_edges == n_nodes / 2
103    }
104}
105
106trait WithDummy: NodeIndexable {
107    fn dummy_idx(&self) -> usize;
108    /// Convert `i` to a node index, returns None for the dummy node
109    fn try_from_index(&self, i: usize) -> Option<Self::NodeId>;
110}
111
112impl<G: NodeIndexable> WithDummy for G {
113    fn dummy_idx(&self) -> usize {
114        // Gabow numbers the vertices from 1 to n, and uses 0 as the dummy
115        // vertex. Our vertex indices are zero-based and so we use the node
116        // bound as the dummy node.
117        self.node_bound()
118    }
119
120    fn try_from_index(&self, i: usize) -> Option<Self::NodeId> {
121        if i != self.dummy_idx() {
122            Some(self.from_index(i))
123        } else {
124            None
125        }
126    }
127}
128
129pub struct MatchedNodes<'a, G: GraphBase> {
130    graph: &'a G,
131    mate: &'a [Option<G::NodeId>],
132    current: usize,
133}
134
135impl<G> Iterator for MatchedNodes<'_, G>
136where
137    G: NodeIndexable,
138{
139    type Item = G::NodeId;
140
141    fn next(&mut self) -> Option<Self::Item> {
142        while self.current != self.mate.len() {
143            let current = self.current;
144            self.current += 1;
145
146            if self.mate[current].is_some() {
147                return Some(self.graph.from_index(current));
148            }
149        }
150
151        None
152    }
153}
154
155pub struct MatchedEdges<'a, G: GraphBase> {
156    graph: &'a G,
157    mate: &'a [Option<G::NodeId>],
158    current: usize,
159}
160
161impl<G> Iterator for MatchedEdges<'_, G>
162where
163    G: NodeIndexable,
164{
165    type Item = (G::NodeId, G::NodeId);
166
167    fn next(&mut self) -> Option<Self::Item> {
168        while self.current != self.mate.len() {
169            let current = self.current;
170            self.current += 1;
171
172            if let Some(mate) = self.mate[current] {
173                // Check if the mate is a node after the current one. If not, then
174                // do not report that edge since it has been already reported (the
175                // graph is considered undirected).
176                if self.graph.to_index(mate) > current {
177                    let this = self.graph.from_index(current);
178                    return Some((this, mate));
179                }
180            }
181        }
182
183        None
184    }
185}
186
187/// \[Generic\] Compute a
188/// [*matching*](https://en.wikipedia.org/wiki/Matching_(graph_theory)) using a
189/// greedy heuristic.
190///
191/// The input graph is treated as if undirected. The underlying heuristic is
192/// unspecified, but is guaranteed to be bounded by **O(|V| + |E|)**. No
193/// guarantees about the output are given other than that it is a valid
194/// matching.
195///
196/// If you require a maximum matching, use [`maximum_matching`][1] function
197/// instead.
198///
199/// # Arguments
200/// * `graph`: an undirected graph.
201///
202/// # Returns
203/// * [`struct@Matching`] calculated using greedy heuristic.
204///
205/// # Complexity
206/// * Time complexity: **O(|V| + |E|)**.
207/// * Auxiliary space: **O(|V|)**.
208///
209/// where **|V|** is the number of nodes and **|E|** is the number of edges.
210///
211/// [1]: fn.maximum_matching.html
212pub fn greedy_matching<G>(graph: G) -> Matching<G>
213where
214    G: Visitable + IntoNodeIdentifiers + NodeIndexable + IntoNeighbors,
215    G::NodeId: Eq + Hash,
216    G::EdgeId: Eq + Hash,
217{
218    let (mates, n_edges) = greedy_matching_inner(&graph);
219    Matching::new(graph, mates, n_edges)
220}
221
222#[inline]
223fn greedy_matching_inner<G>(graph: &G) -> (Vec<Option<G::NodeId>>, usize)
224where
225    G: Visitable + IntoNodeIdentifiers + NodeIndexable + IntoNeighbors,
226{
227    let mut mate = vec![None; graph.node_bound()];
228    let mut n_edges = 0;
229    let visited = &mut graph.visit_map();
230
231    for start in graph.node_identifiers() {
232        let mut last = Some(start);
233
234        // Function non_backtracking_dfs does not expand the node if it has been
235        // already visited.
236        non_backtracking_dfs(graph, start, visited, |next| {
237            // Alternate matched and unmatched edges.
238            if let Some(pred) = last.take() {
239                mate[graph.to_index(pred)] = Some(next);
240                mate[graph.to_index(next)] = Some(pred);
241                n_edges += 1;
242            } else {
243                last = Some(next);
244            }
245        });
246    }
247
248    (mate, n_edges)
249}
250
251fn non_backtracking_dfs<G, F>(graph: &G, source: G::NodeId, visited: &mut G::Map, mut visitor: F)
252where
253    G: Visitable + IntoNeighbors,
254    F: FnMut(G::NodeId),
255{
256    if visited.visit(source) {
257        for target in graph.neighbors(source) {
258            if !visited.is_visited(&target) {
259                visitor(target);
260                non_backtracking_dfs(graph, target, visited, visitor);
261
262                // Non-backtracking traversal, stop iterating over the
263                // neighbors.
264                break;
265            }
266        }
267    }
268}
269
270#[derive(Clone, Copy, Default)]
271enum Label<G: GraphBase> {
272    #[default]
273    None,
274    Start,
275    // If node v is outer node, then label(v) = w is another outer node on path
276    // from v to start u.
277    Vertex(G::NodeId),
278    // If node v is outer node, then label(v) = (r, s) are two outer vertices
279    // (connected by an edge)
280    Edge(G::EdgeId, [G::NodeId; 2]),
281    // Flag is a special label used in searching for the join vertex of two
282    // paths.
283    Flag(G::EdgeId),
284}
285
286impl<G: GraphBase> Label<G> {
287    fn is_outer(&self) -> bool {
288        self != &Label::None && !matches!(self, Label::Flag(_))
289    }
290
291    fn is_inner(&self) -> bool {
292        !self.is_outer()
293    }
294
295    fn to_vertex(&self) -> Option<G::NodeId> {
296        match *self {
297            Label::Vertex(v) => Some(v),
298            _ => None,
299        }
300    }
301
302    fn is_flagged(&self, edge: G::EdgeId) -> bool {
303        matches!(self, Label::Flag(flag) if flag == &edge)
304    }
305}
306
307impl<G: GraphBase> PartialEq for Label<G> {
308    fn eq(&self, other: &Self) -> bool {
309        match (self, other) {
310            (Label::None, Label::None) => true,
311            (Label::Start, Label::Start) => true,
312            (Label::Vertex(v1), Label::Vertex(v2)) => v1 == v2,
313            (Label::Edge(e1, _), Label::Edge(e2, _)) => e1 == e2,
314            (Label::Flag(e1), Label::Flag(e2)) => e1 == e2,
315            _ => false,
316        }
317    }
318}
319
320/// \[Generic\] Compute the [*maximum
321/// matching*](https://en.wikipedia.org/wiki/Matching_(graph_theory)) using
322/// [Gabow's algorithm][1].
323///
324/// [1]: https://dl.acm.org/doi/10.1145/321941.321942
325///
326/// The input graph is treated as if undirected. The algorithm runs in
327/// **O(|V|³)**. An algorithm with a better time complexity might be used in the
328/// future.
329///
330/// **Panics** if `g.node_bound()` is `usize::MAX`.
331///
332/// # Arguments
333/// * `graph`: an undirected graph.
334///
335/// # Returns
336/// * [`struct@Matching`]: computed maximum matching.
337///
338/// # Complexity
339/// * Time complexity: **O(|V|³)**.
340/// * Auxiliary space: **O(|V| + |E|)**.
341///
342/// where **|V|** is the number of nodes and **|E|** is the number of edges.
343///
344/// # Examples
345///
346/// ```
347/// use petgraph::prelude::*;
348/// use petgraph::algo::maximum_matching;
349///
350/// // The example graph:
351/// //
352/// //    +-- b ---- d ---- f
353/// //   /    |      |
354/// //  a     |      |
355/// //   \    |      |
356/// //    +-- c ---- e
357/// //
358/// // Maximum matching: { (a, b), (c, e), (d, f) }
359///
360/// let mut graph: UnGraph<(), ()> = UnGraph::new_undirected();
361/// let a = graph.add_node(());
362/// let b = graph.add_node(());
363/// let c = graph.add_node(());
364/// let d = graph.add_node(());
365/// let e = graph.add_node(());
366/// let f = graph.add_node(());
367/// graph.extend_with_edges(&[(a, b), (a, c), (b, c), (b, d), (c, e), (d, e), (d, f)]);
368///
369/// let matching = maximum_matching(&graph);
370/// assert!(matching.contains_edge(a, b));
371/// assert!(matching.contains_edge(c, e));
372/// assert_eq!(matching.mate(d), Some(f));
373/// assert_eq!(matching.mate(f), Some(d));
374/// ```
375pub fn maximum_matching<G>(graph: G) -> Matching<G>
376where
377    G: Visitable + NodeIndexable + IntoNodeIdentifiers + IntoEdges,
378{
379    // The dummy identifier needs an unused index
380    assert_ne!(
381        graph.node_bound(),
382        usize::MAX,
383        "The input graph capacity should be strictly less than core::usize::MAX."
384    );
385
386    // Greedy algorithm should create a fairly good initial matching. The hope
387    // is that it speeds up the computation by doing les work in the complex
388    // algorithm.
389    let (mut mate, mut n_edges) = greedy_matching_inner(&graph);
390
391    // Gabow's algorithm uses a dummy node in the mate array.
392    mate.push(None);
393    let len = graph.node_bound() + 1;
394    debug_assert_eq!(mate.len(), len);
395
396    let mut label: Vec<Label<G>> = vec![Label::None; len];
397    let mut first_inner = vec![usize::MAX; len];
398    let visited = &mut graph.visit_map();
399
400    // Queue will contain outer vertices that should be processed next.
401    // The queue is cleared after each iteration of the main loop.
402    let mut queue = VecDeque::new();
403
404    for start in 0..graph.node_bound() {
405        if mate[start].is_some() {
406            // The vertex is already matched. A start must be a free vertex.
407            continue;
408        }
409
410        // Begin search from the node.
411        label[start] = Label::Start;
412        first_inner[start] = graph.dummy_idx();
413        graph.reset_map(visited);
414
415        // start is never a dummy index
416        let start = graph.from_index(start);
417
418        // The start vertex is considered a first outer vertex on each iteration.
419        queue.push_back(start);
420        // Mark the start vertex so it is not processed repeatedly.
421        visited.visit(start);
422
423        'search: while let Some(outer_vertex) = queue.pop_front() {
424            for edge in graph.edges(outer_vertex) {
425                if edge.source() == edge.target() {
426                    // Ignore self-loops.
427                    continue;
428                }
429
430                let other_vertex = edge.target();
431                let other_idx = graph.to_index(other_vertex);
432
433                if mate[other_idx].is_none() && other_vertex != start {
434                    // An augmenting path was found. Augment the matching. If
435                    // `other` is actually the start node, then the augmentation
436                    // must not be performed, because the start vertex would be
437                    // incident to two edges, which violates the matching
438                    // property.
439                    mate[other_idx] = Some(outer_vertex);
440                    augment_path(&graph, outer_vertex, other_vertex, &mut mate, &label);
441                    n_edges += 1;
442
443                    // The path is augmented, so the start is no longer free
444                    // vertex. We need to begin with a new start.
445                    break 'search;
446                } else if label[other_idx].is_outer() {
447                    // The `other` is an outer vertex (a label has been set to
448                    // it). An odd cycle (blossom) was found. Assign this edge
449                    // as a label to all inner vertices in paths P(outer) and
450                    // P(other).
451                    find_join(
452                        &graph,
453                        edge,
454                        &mate,
455                        &mut label,
456                        &mut first_inner,
457                        |labeled| {
458                            if visited.visit(labeled) {
459                                queue.push_back(labeled);
460                            }
461                        },
462                    );
463                } else {
464                    let mate_vertex = mate[other_idx];
465                    let mate_idx = mate_vertex.map_or(graph.dummy_idx(), |id| graph.to_index(id));
466
467                    if label[mate_idx].is_inner() {
468                        // Mate of `other` vertex is inner (no label has been
469                        // set to it so far). But it actually is an outer vertex
470                        // (it is on a path to the start vertex that begins with
471                        // a matched edge, since it is a mate of `other`).
472                        // Assign the label of this mate to the `outer` vertex,
473                        // so the path for it can be reconstructed using `mate`
474                        // and this label.
475                        label[mate_idx] = Label::Vertex(outer_vertex);
476                        first_inner[mate_idx] = other_idx;
477                    }
478
479                    // Add the vertex to the queue only if it's not the dummy and this is its first
480                    // discovery.
481                    if let Some(mate_vertex) = mate_vertex {
482                        if visited.visit(mate_vertex) {
483                            queue.push_back(mate_vertex);
484                        }
485                    }
486                }
487            }
488        }
489
490        // Reset the labels. All vertices are inner for the next search.
491        for lbl in label.iter_mut() {
492            *lbl = Label::None;
493        }
494
495        queue.clear();
496    }
497
498    // Discard the dummy node.
499    mate.pop();
500
501    Matching::new(graph, mate, n_edges)
502}
503
504fn find_join<G, F>(
505    graph: &G,
506    edge: G::EdgeRef,
507    mate: &[Option<G::NodeId>],
508    label: &mut [Label<G>],
509    first_inner: &mut [usize],
510    mut visitor: F,
511) where
512    G: IntoEdges + NodeIndexable + Visitable,
513    F: FnMut(G::NodeId),
514{
515    // Simultaneously traverse the inner vertices on paths P(source) and
516    // P(target) to find a join vertex - an inner vertex that is shared by these
517    // paths.
518    let source = graph.to_index(edge.source());
519    let target = graph.to_index(edge.target());
520
521    let mut left = first_inner[source];
522    let mut right = first_inner[target];
523
524    if left == right {
525        // No vertices can be labeled, since both paths already refer to a
526        // common vertex - the join.
527        return;
528    }
529
530    // Flag the (first) inner vertices. This ensures that they are assigned the
531    // join as their first inner vertex.
532    let flag = Label::Flag(edge.id());
533    label[left] = flag;
534    label[right] = flag;
535
536    // Find the join.
537    let join = loop {
538        // Swap the sides. Do not swap if the right side is already finished.
539        if right != graph.dummy_idx() {
540            core::mem::swap(&mut left, &mut right);
541        }
542
543        // Set left to the next inner vertex in P(source) or P(target).
544        // The unwraps are safe because left is not the dummy node.
545        let left_mate = graph.to_index(mate[left].unwrap());
546        let next_inner = label[left_mate].to_vertex().unwrap();
547        left = first_inner[graph.to_index(next_inner)];
548
549        if !label[left].is_flagged(edge.id()) {
550            // The inner vertex is not flagged yet, so flag it.
551            label[left] = flag;
552        } else {
553            // The inner vertex is already flagged. It means that the other side
554            // had to visit it already. Therefore it is the join vertex.
555            break left;
556        }
557    };
558
559    // Label all inner vertices on P(source) and P(target) with the found join.
560    for endpoint in [source, target].iter().copied() {
561        let mut inner = first_inner[endpoint];
562        while inner != join {
563            // Notify the caller about labeling a vertex.
564            if let Some(ix) = graph.try_from_index(inner) {
565                visitor(ix);
566            }
567
568            label[inner] = Label::Edge(edge.id(), [edge.source(), edge.target()]);
569            first_inner[inner] = join;
570            let inner_mate = graph.to_index(mate[inner].unwrap());
571            let next_inner = label[inner_mate].to_vertex().unwrap();
572            inner = first_inner[graph.to_index(next_inner)];
573        }
574    }
575
576    for (vertex_idx, vertex_label) in label.iter().enumerate() {
577        // To all outer vertices that are on paths P(source) and P(target) until
578        // the join, se the join as their first inner vertex.
579        if vertex_idx != graph.dummy_idx()
580            && vertex_label.is_outer()
581            && label[first_inner[vertex_idx]].is_outer()
582        {
583            first_inner[vertex_idx] = join;
584        }
585    }
586}
587
588fn augment_path<G>(
589    graph: &G,
590    outer: G::NodeId,
591    other: G::NodeId,
592    mate: &mut [Option<G::NodeId>],
593    label: &[Label<G>],
594) where
595    G: NodeIndexable,
596{
597    let outer_idx = graph.to_index(outer);
598
599    let temp = mate[outer_idx];
600    let temp_idx = temp.map_or(graph.dummy_idx(), |id| graph.to_index(id));
601    mate[outer_idx] = Some(other);
602
603    if mate[temp_idx] != Some(outer) {
604        // We are at the end of the path and so the entire path is completely
605        // rematched/augmented.
606    } else if let Label::Vertex(vertex) = label[outer_idx] {
607        // The outer vertex has a vertex label which refers to another outer
608        // vertex on the path. So we set this another outer node as the mate for
609        // the previous mate of the outer node.
610        mate[temp_idx] = Some(vertex);
611        if let Some(temp) = temp {
612            augment_path(graph, vertex, temp, mate, label);
613        }
614    } else if let Label::Edge(_, [source, target]) = label[outer_idx] {
615        // The outer vertex has an edge label which refers to an edge in a
616        // blossom. We need to augment both directions along the blossom.
617        augment_path(graph, source, target, mate, label);
618        augment_path(graph, target, source, mate, label);
619    } else {
620        panic!("Unexpected label when augmenting path");
621    }
622}