use std::collections::{HashMap, HashSet};
use crate::graph::capability::{Bigraph, StableEdge};
use crate::graph::Graph;
type Adjacency<G> = HashMap<
<G as crate::graph::GraphProperty>::NodeIx,
Vec<(
<G as crate::graph::GraphProperty>::EdgeIx,
<G as crate::graph::GraphProperty>::NodeIx,
)>,
>;
pub struct GreedyMatching<'r, G: ?Sized, E, N> {
graph: &'r G,
edges: E,
matched_nodes: HashSet<N>,
}
pub fn greedy_matching<'r, G>(graph: &'r G) -> GreedyMatching<'r, G, <G as crate::graph::GraphOperation<'r>>::EdgeIndices, G::NodeIx>
where
G: Graph + Bigraph + StableEdge + ?Sized,
{
GreedyMatching {
graph,
edges: <_ as crate::graph::GraphOperation<'_>>::edge_indices(graph),
matched_nodes: HashSet::new(),
}
}
impl<'r, G> Iterator for GreedyMatching<'r, G, <G as crate::graph::GraphOperation<'r>>::EdgeIndices, G::NodeIx>
where
G: Graph + Bigraph + StableEdge + ?Sized,
{
type Item = G::EdgeIx;
fn next(&mut self) -> Option<G::EdgeIx> {
loop {
let eix = self.edges.next()?;
let eps: Vec<G::NodeIx> = unsafe { <G as crate::graph::GraphOperation<'_>>::endpoints_unchecked(self.graph, eix) }
.into_iter()
.collect();
let (a, b) = (eps[0], eps[1]);
if a == b {
continue;
}
if !self.matched_nodes.contains(&a) && !self.matched_nodes.contains(&b) {
self.matched_nodes.insert(a);
self.matched_nodes.insert(b);
return Some(eix);
}
}
}
}
pub fn max_matching<G>(graph: &G) -> HashSet<G::EdgeIx>
where
G: Graph + Bigraph + StableEdge + ?Sized,
{
let mut adj: Adjacency<G> = HashMap::new();
for eix in <_ as crate::graph::GraphOperation<'_>>::edge_indices(graph) {
let eps: Vec<G::NodeIx> = unsafe { <G as crate::graph::GraphOperation<'_>>::endpoints_unchecked(graph, eix) }
.into_iter()
.collect();
let (a, b) = (eps[0], eps[1]);
if a == b {
continue; }
adj.entry(a).or_default().push((eix, b));
adj.entry(b).or_default().push((eix, a));
}
let mut match_of: HashMap<G::NodeIx, (G::EdgeIx, G::NodeIx)> = HashMap::new();
let mut in_matching: HashSet<G::EdgeIx> = HashSet::new();
for eix in <_ as crate::graph::GraphOperation<'_>>::edge_indices(graph) {
let eps: Vec<G::NodeIx> = unsafe { <G as crate::graph::GraphOperation<'_>>::endpoints_unchecked(graph, eix) }
.into_iter()
.collect();
let (a, b) = (eps[0], eps[1]);
if a == b {
continue;
}
if !match_of.contains_key(&a) && !match_of.contains_key(&b) {
match_of.insert(a, (eix, b));
match_of.insert(b, (eix, a));
in_matching.insert(eix);
}
}
loop {
let free_nodes: Vec<G::NodeIx> = <_ as crate::graph::GraphOperation<'_>>::node_indices(graph)
.filter(|n| !match_of.contains_key(n))
.collect();
let mut found_augmenting = false;
for free in &free_nodes {
if match_of.contains_key(free) {
continue; }
if let Some(path) = find_augmenting_path(*free, &adj, &match_of, &in_matching) {
for (eix, in_match) in path {
if in_match {
in_matching.remove(&eix);
} else {
in_matching.insert(eix);
}
}
match_of.clear();
for &eix in &in_matching {
let eps: Vec<G::NodeIx> = unsafe { <G as crate::graph::GraphOperation<'_>>::endpoints_unchecked(graph, eix) }
.into_iter()
.collect();
let (a, b) = (eps[0], eps[1]);
match_of.insert(a, (eix, b));
match_of.insert(b, (eix, a));
}
found_augmenting = true;
}
}
if !found_augmenting {
break;
}
}
in_matching
}
fn find_augmenting_path<N, E>(
start: N,
adj: &HashMap<N, Vec<(E, N)>>,
match_of: &HashMap<N, (E, N)>,
in_matching: &HashSet<E>,
) -> Option<Vec<(E, bool)>>
where
N: Copy + Eq + std::hash::Hash,
E: Copy + Eq + std::hash::Hash,
{
let mut visited: HashSet<N> = HashSet::new();
visited.insert(start);
struct Frame<N> {
node: N,
use_unmatched: bool, neighbors_idx: usize,
}
let mut stack: Vec<Frame<N>> = vec![Frame {
node: start,
use_unmatched: true, neighbors_idx: 0,
}];
let mut path: Vec<(E, bool)> = Vec::new();
loop {
let stack_len = stack.len();
if stack_len == 0 {
break;
}
let frame = &mut stack[stack_len - 1];
let empty_vec = Vec::new();
let neighbors = adj.get(&frame.node).unwrap_or(&empty_vec);
if frame.neighbors_idx >= neighbors.len() {
stack.pop();
path.pop();
if let Some(top) = stack.last_mut() {
top.neighbors_idx += 1;
}
continue;
}
let (eix, neighbor) = neighbors[frame.neighbors_idx];
let edge_in_matching = in_matching.contains(&eix);
if frame.use_unmatched == edge_in_matching {
frame.neighbors_idx += 1;
continue;
}
if visited.contains(&neighbor) {
frame.neighbors_idx += 1;
continue;
}
let next_use_unmatched = !frame.use_unmatched;
path.push((eix, edge_in_matching));
visited.insert(neighbor);
if !edge_in_matching && !match_of.contains_key(&neighbor) {
return Some(path);
}
stack.push(Frame {
node: neighbor,
use_unmatched: next_use_unmatched,
neighbors_idx: 0,
});
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BTreeGraph;
#[test]
fn greedy_matching_basic() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
let m: HashSet<_> = greedy_matching(&g).collect();
assert!(!m.is_empty());
assert!(m.len() <= 1);
}
#[test]
fn max_matching_path() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_node(3).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
g.insert_edge("2->3", [2, 3]).unwrap();
let m = max_matching(&g);
assert_eq!(m.len(), 2);
}
#[test]
fn max_matching_star() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_node(3).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("0->2", [0, 2]).unwrap();
g.insert_edge("0->3", [0, 3]).unwrap();
let m = max_matching(&g);
assert_eq!(m.len(), 1);
}
#[test]
fn max_matching_complete_bipartite() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_node(3).unwrap();
g.insert_edge("0->2", [0, 2]).unwrap();
g.insert_edge("0->3", [0, 3]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
g.insert_edge("1->3", [1, 3]).unwrap();
let m = max_matching(&g);
assert_eq!(m.len(), 2);
}
#[test]
fn matching_no_shared_vertices() {
let mut g = BTreeGraph::<_, _>::default();
g.insert_node(0).unwrap();
g.insert_node(1).unwrap();
g.insert_node(2).unwrap();
g.insert_node(3).unwrap();
g.insert_node(4).unwrap();
g.insert_edge("0->1", [0, 1]).unwrap();
g.insert_edge("1->2", [1, 2]).unwrap();
g.insert_edge("2->3", [2, 3]).unwrap();
g.insert_edge("3->4", [3, 4]).unwrap();
let m = max_matching(&g);
let mut used_nodes: HashSet<i32> = HashSet::new();
for &eix in &m {
let tail = g.edge_tail_index(eix);
let head = g.edge_head_index(eix);
assert!(used_nodes.insert(tail), "Node {:?} used twice", tail);
assert!(used_nodes.insert(head), "Node {:?} used twice", head);
}
assert_eq!(m.len(), 2);
}
#[test]
fn matching_empty_graph() {
let g = BTreeGraph::<u32, &str>::default();
let m: HashSet<_> = greedy_matching(&g).collect();
assert!(m.is_empty());
}
}