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/// Compute a [*matching*](https://en.wikipedia.org/wiki/Matching_(graph_theory)) using a
188/// greedy heuristic.
189///
190/// The input graph is treated as if undirected. The underlying heuristic is
191/// unspecified, but is guaranteed to be bounded by **O(|V| + |E|)**. No
192/// guarantees about the output are given other than that it is a valid
193/// matching.
194///
195/// If you require a maximum matching, use [`maximum_matching`][1] function
196/// instead.
197///
198/// # Arguments
199/// * `graph`: an undirected graph.
200///
201/// # Returns
202/// * [`struct@Matching`] calculated using greedy heuristic.
203///
204/// # Complexity
205/// * Time complexity: **O(|V| + |E|)**.
206/// * Auxiliary space: **O(|V|)**.
207///
208/// where **|V|** is the number of nodes and **|E|** is the number of edges.
209///
210/// [1]: fn.maximum_matching.html
211pub fn greedy_matching<G>(graph: G) -> Matching<G>
212where
213 G: Visitable + IntoNodeIdentifiers + NodeIndexable + IntoNeighbors,
214 G::NodeId: Eq + Hash,
215 G::EdgeId: Eq + Hash,
216{
217 let (mates, n_edges) = greedy_matching_inner(&graph);
218 Matching::new(graph, mates, n_edges)
219}
220
221#[inline]
222fn greedy_matching_inner<G>(graph: &G) -> (Vec<Option<G::NodeId>>, usize)
223where
224 G: Visitable + IntoNodeIdentifiers + NodeIndexable + IntoNeighbors,
225{
226 let mut mate = vec![None; graph.node_bound()];
227 let mut n_edges = 0;
228 let visited = &mut graph.visit_map();
229
230 for start in graph.node_identifiers() {
231 let mut last = Some(start);
232
233 // Function non_backtracking_dfs does not expand the node if it has been
234 // already visited.
235 non_backtracking_dfs(graph, start, visited, |next| {
236 // Alternate matched and unmatched edges.
237 if let Some(pred) = last.take() {
238 mate[graph.to_index(pred)] = Some(next);
239 mate[graph.to_index(next)] = Some(pred);
240 n_edges += 1;
241 } else {
242 last = Some(next);
243 }
244 });
245 }
246
247 (mate, n_edges)
248}
249
250fn non_backtracking_dfs<G, F>(graph: &G, source: G::NodeId, visited: &mut G::Map, mut visitor: F)
251where
252 G: Visitable + IntoNeighbors,
253 F: FnMut(G::NodeId),
254{
255 if visited.visit(source) {
256 for target in graph.neighbors(source) {
257 if !visited.is_visited(&target) {
258 visitor(target);
259 non_backtracking_dfs(graph, target, visited, visitor);
260
261 // Non-backtracking traversal, stop iterating over the
262 // neighbors.
263 break;
264 }
265 }
266 }
267}
268
269#[derive(Clone, Copy, Default)]
270enum Label<G: GraphBase> {
271 #[default]
272 None,
273 Start,
274 // If node v is outer node, then label(v) = w is another outer node on path
275 // from v to start u.
276 Vertex(G::NodeId),
277 // If node v is outer node, then label(v) = (r, s) are two outer vertices
278 // (connected by an edge)
279 Edge(G::EdgeId, [G::NodeId; 2]),
280 // Flag is a special label used in searching for the join vertex of two
281 // paths.
282 Flag(G::EdgeId),
283}
284
285impl<G: GraphBase> Label<G> {
286 fn is_outer(&self) -> bool {
287 self != &Label::None && !matches!(self, Label::Flag(_))
288 }
289
290 fn is_inner(&self) -> bool {
291 !self.is_outer()
292 }
293
294 fn to_vertex(&self) -> Option<G::NodeId> {
295 match *self {
296 Label::Vertex(v) => Some(v),
297 _ => None,
298 }
299 }
300
301 fn is_flagged(&self, edge: G::EdgeId) -> bool {
302 matches!(self, Label::Flag(flag) if flag == &edge)
303 }
304}
305
306impl<G: GraphBase> PartialEq for Label<G> {
307 fn eq(&self, other: &Self) -> bool {
308 match (self, other) {
309 (Label::None, Label::None) => true,
310 (Label::Start, Label::Start) => true,
311 (Label::Vertex(v1), Label::Vertex(v2)) => v1 == v2,
312 (Label::Edge(e1, _), Label::Edge(e2, _)) => e1 == e2,
313 (Label::Flag(e1), Label::Flag(e2)) => e1 == e2,
314 _ => false,
315 }
316 }
317}
318
319/// Compute the [*maximum matching*](https://en.wikipedia.org/wiki/Matching_(graph_theory)) using
320/// [Gabow's algorithm][1].
321///
322/// [1]: https://dl.acm.org/doi/10.1145/321941.321942
323///
324/// The input graph is treated as if undirected. The algorithm runs in
325/// **O(|V|³)**. An algorithm with a better time complexity might be used in the
326/// future.
327///
328/// **Panics** if `g.node_bound()` is `usize::MAX`.
329///
330/// # Arguments
331/// * `graph`: an undirected graph.
332///
333/// # Returns
334/// * [`struct@Matching`]: computed maximum matching.
335///
336/// # Complexity
337/// * Time complexity: **O(|V|³)**.
338/// * Auxiliary space: **O(|V| + |E|)**.
339///
340/// where **|V|** is the number of nodes and **|E|** is the number of edges.
341///
342/// # Examples
343///
344/// ```
345/// use petgraph::prelude::*;
346/// use petgraph::algo::maximum_matching;
347///
348/// // The example graph:
349/// //
350/// // +-- b ---- d ---- f
351/// // / | |
352/// // a | |
353/// // \ | |
354/// // +-- c ---- e
355/// //
356/// // Maximum matching: { (a, b), (c, e), (d, f) }
357///
358/// let mut graph: UnGraph<(), ()> = UnGraph::new_undirected();
359/// let a = graph.add_node(());
360/// let b = graph.add_node(());
361/// let c = graph.add_node(());
362/// let d = graph.add_node(());
363/// let e = graph.add_node(());
364/// let f = graph.add_node(());
365/// graph.extend_with_edges(&[(a, b), (a, c), (b, c), (b, d), (c, e), (d, e), (d, f)]);
366///
367/// let matching = maximum_matching(&graph);
368/// assert!(matching.contains_edge(a, b));
369/// assert!(matching.contains_edge(c, e));
370/// assert_eq!(matching.mate(d), Some(f));
371/// assert_eq!(matching.mate(f), Some(d));
372/// ```
373pub fn maximum_matching<G>(graph: G) -> Matching<G>
374where
375 G: Visitable + NodeIndexable + IntoNodeIdentifiers + IntoEdges,
376{
377 // The dummy identifier needs an unused index
378 assert_ne!(
379 graph.node_bound(),
380 usize::MAX,
381 "The input graph capacity should be strictly less than core::usize::MAX."
382 );
383
384 // Greedy algorithm should create a fairly good initial matching. The hope
385 // is that it speeds up the computation by doing les work in the complex
386 // algorithm.
387 let (mut mate, mut n_edges) = greedy_matching_inner(&graph);
388
389 // Gabow's algorithm uses a dummy node in the mate array.
390 mate.push(None);
391 let len = graph.node_bound() + 1;
392 debug_assert_eq!(mate.len(), len);
393
394 let mut label: Vec<Label<G>> = vec![Label::None; len];
395 let mut first_inner = vec![usize::MAX; len];
396 let visited = &mut graph.visit_map();
397
398 // Queue will contain outer vertices that should be processed next.
399 // The queue is cleared after each iteration of the main loop.
400 let mut queue = VecDeque::new();
401
402 for start in 0..graph.node_bound() {
403 if mate[start].is_some() {
404 // The vertex is already matched. A start must be a free vertex.
405 continue;
406 }
407
408 // Begin search from the node.
409 label[start] = Label::Start;
410 first_inner[start] = graph.dummy_idx();
411 graph.reset_map(visited);
412
413 // start is never a dummy index
414 let start = graph.from_index(start);
415
416 // The start vertex is considered a first outer vertex on each iteration.
417 queue.push_back(start);
418 // Mark the start vertex so it is not processed repeatedly.
419 visited.visit(start);
420
421 'search: while let Some(outer_vertex) = queue.pop_front() {
422 for edge in graph.edges(outer_vertex) {
423 if edge.source() == edge.target() {
424 // Ignore self-loops.
425 continue;
426 }
427
428 let other_vertex = edge.target();
429 let other_idx = graph.to_index(other_vertex);
430
431 if mate[other_idx].is_none() && other_vertex != start {
432 // An augmenting path was found. Augment the matching. If
433 // `other` is actually the start node, then the augmentation
434 // must not be performed, because the start vertex would be
435 // incident to two edges, which violates the matching
436 // property.
437 mate[other_idx] = Some(outer_vertex);
438 augment_path(&graph, outer_vertex, other_vertex, &mut mate, &label);
439 n_edges += 1;
440
441 // The path is augmented, so the start is no longer free
442 // vertex. We need to begin with a new start.
443 break 'search;
444 } else if label[other_idx].is_outer() {
445 // The `other` is an outer vertex (a label has been set to
446 // it). An odd cycle (blossom) was found. Assign this edge
447 // as a label to all inner vertices in paths P(outer) and
448 // P(other).
449 find_join(
450 &graph,
451 edge,
452 &mate,
453 &mut label,
454 &mut first_inner,
455 |labeled| {
456 if visited.visit(labeled) {
457 queue.push_back(labeled);
458 }
459 },
460 );
461 } else {
462 let mate_vertex = mate[other_idx];
463 let mate_idx = mate_vertex.map_or(graph.dummy_idx(), |id| graph.to_index(id));
464
465 if label[mate_idx].is_inner() {
466 // Mate of `other` vertex is inner (no label has been
467 // set to it so far). But it actually is an outer vertex
468 // (it is on a path to the start vertex that begins with
469 // a matched edge, since it is a mate of `other`).
470 // Assign the label of this mate to the `outer` vertex,
471 // so the path for it can be reconstructed using `mate`
472 // and this label.
473 label[mate_idx] = Label::Vertex(outer_vertex);
474 first_inner[mate_idx] = other_idx;
475 }
476
477 // Add the vertex to the queue only if it's not the dummy and this is its first
478 // discovery.
479 if let Some(mate_vertex) = mate_vertex {
480 if visited.visit(mate_vertex) {
481 queue.push_back(mate_vertex);
482 }
483 }
484 }
485 }
486 }
487
488 // Reset the labels. All vertices are inner for the next search.
489 for lbl in label.iter_mut() {
490 *lbl = Label::None;
491 }
492
493 queue.clear();
494 }
495
496 // Discard the dummy node.
497 mate.pop();
498
499 Matching::new(graph, mate, n_edges)
500}
501
502fn find_join<G, F>(
503 graph: &G,
504 edge: G::EdgeRef,
505 mate: &[Option<G::NodeId>],
506 label: &mut [Label<G>],
507 first_inner: &mut [usize],
508 mut visitor: F,
509) where
510 G: IntoEdges + NodeIndexable + Visitable,
511 F: FnMut(G::NodeId),
512{
513 // Simultaneously traverse the inner vertices on paths P(source) and
514 // P(target) to find a join vertex - an inner vertex that is shared by these
515 // paths.
516 let source = graph.to_index(edge.source());
517 let target = graph.to_index(edge.target());
518
519 let mut left = first_inner[source];
520 let mut right = first_inner[target];
521
522 if left == right {
523 // No vertices can be labeled, since both paths already refer to a
524 // common vertex - the join.
525 return;
526 }
527
528 // Flag the (first) inner vertices. This ensures that they are assigned the
529 // join as their first inner vertex.
530 let flag = Label::Flag(edge.id());
531 label[left] = flag;
532 label[right] = flag;
533
534 // Find the join.
535 let join = loop {
536 // Swap the sides. Do not swap if the right side is already finished.
537 if right != graph.dummy_idx() {
538 core::mem::swap(&mut left, &mut right);
539 }
540
541 // Set left to the next inner vertex in P(source) or P(target).
542 // The unwraps are safe because left is not the dummy node.
543 let left_mate = graph.to_index(mate[left].unwrap());
544 let next_inner = label[left_mate].to_vertex().unwrap();
545 left = first_inner[graph.to_index(next_inner)];
546
547 if !label[left].is_flagged(edge.id()) {
548 // The inner vertex is not flagged yet, so flag it.
549 label[left] = flag;
550 } else {
551 // The inner vertex is already flagged. It means that the other side
552 // had to visit it already. Therefore it is the join vertex.
553 break left;
554 }
555 };
556
557 // Label all inner vertices on P(source) and P(target) with the found join.
558 for endpoint in [source, target].iter().copied() {
559 let mut inner = first_inner[endpoint];
560 while inner != join {
561 // Notify the caller about labeling a vertex.
562 if let Some(ix) = graph.try_from_index(inner) {
563 visitor(ix);
564 }
565
566 label[inner] = Label::Edge(edge.id(), [edge.source(), edge.target()]);
567 first_inner[inner] = join;
568 let inner_mate = graph.to_index(mate[inner].unwrap());
569 let next_inner = label[inner_mate].to_vertex().unwrap();
570 inner = first_inner[graph.to_index(next_inner)];
571 }
572 }
573
574 for (vertex_idx, vertex_label) in label.iter().enumerate() {
575 // To all outer vertices that are on paths P(source) and P(target) until
576 // the join, se the join as their first inner vertex.
577 if vertex_idx != graph.dummy_idx()
578 && vertex_label.is_outer()
579 && label[first_inner[vertex_idx]].is_outer()
580 {
581 first_inner[vertex_idx] = join;
582 }
583 }
584}
585
586fn augment_path<G>(
587 graph: &G,
588 outer: G::NodeId,
589 other: G::NodeId,
590 mate: &mut [Option<G::NodeId>],
591 label: &[Label<G>],
592) where
593 G: NodeIndexable,
594{
595 let outer_idx = graph.to_index(outer);
596
597 let temp = mate[outer_idx];
598 let temp_idx = temp.map_or(graph.dummy_idx(), |id| graph.to_index(id));
599 mate[outer_idx] = Some(other);
600
601 if mate[temp_idx] != Some(outer) {
602 // We are at the end of the path and so the entire path is completely
603 // rematched/augmented.
604 } else if let Label::Vertex(vertex) = label[outer_idx] {
605 // The outer vertex has a vertex label which refers to another outer
606 // vertex on the path. So we set this another outer node as the mate for
607 // the previous mate of the outer node.
608 mate[temp_idx] = Some(vertex);
609 if let Some(temp) = temp {
610 augment_path(graph, vertex, temp, mate, label);
611 }
612 } else if let Label::Edge(_, [source, target]) = label[outer_idx] {
613 // The outer vertex has an edge label which refers to an edge in a
614 // blossom. We need to augment both directions along the blossom.
615 augment_path(graph, source, target, mate, label);
616 augment_path(graph, target, source, mate, label);
617 } else {
618 panic!("Unexpected label when augmenting path");
619 }
620}