use std::collections::VecDeque;
const UNMATCHED: usize = usize::MAX;
pub fn hopcroft_karp(
left_n: usize,
right_n: usize,
edges: &[(usize, usize)],
) -> Vec<Option<usize>> {
if left_n == 0 || right_n == 0 {
return vec![None; left_n];
}
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); left_n];
for &(l, r) in edges {
if l < left_n && r < right_n {
adj[l].push(r);
}
}
let mut match_left: Vec<usize> = vec![UNMATCHED; left_n];
let mut match_right: Vec<usize> = vec![UNMATCHED; right_n];
loop {
let dist = bfs_phase(&adj, &match_left, &match_right, left_n, right_n);
if dist[left_n] == UNMATCHED {
break;
}
let mut dist_dfs = dist.clone();
for u in 0..left_n {
if match_left[u] == UNMATCHED {
dfs_augment(
u,
&adj,
&mut match_left,
&mut match_right,
&mut dist_dfs,
left_n,
);
}
}
}
match_left
.into_iter()
.map(|v| if v == UNMATCHED { None } else { Some(v) })
.collect()
}
fn bfs_phase(
adj: &[Vec<usize>],
match_left: &[usize],
match_right: &[usize],
left_n: usize,
right_n: usize,
) -> Vec<usize> {
let mut dist = vec![UNMATCHED; left_n + 1];
let mut queue = VecDeque::new();
for u in 0..left_n {
if match_left[u] == UNMATCHED {
dist[u] = 0;
queue.push_back(u);
}
}
while let Some(u) = queue.pop_front() {
let du = dist[u];
if du == UNMATCHED {
continue;
}
let sink_dist = dist[left_n];
if sink_dist != UNMATCHED && du >= sink_dist {
continue;
}
for &v in &adj[u] {
if v >= right_n {
continue;
}
let w = match_right[v];
let w_idx = if w == UNMATCHED { left_n } else { w };
if dist[w_idx] == UNMATCHED {
dist[w_idx] = du + 1;
if w_idx != left_n {
queue.push_back(w_idx);
}
}
}
}
dist
}
fn dfs_augment(
start: usize,
adj: &[Vec<usize>],
match_left: &mut [usize],
match_right: &mut [usize],
dist: &mut [usize],
left_n: usize,
) -> bool {
let mut call_stack: Vec<(usize, usize)> = vec![(start, 0)];
let mut right_path: Vec<usize> = Vec::new();
'outer: while let Some(top) = call_stack.last_mut() {
let (u, ref mut ei) = *top;
let edges = &adj[u];
loop {
if *ei >= edges.len() {
call_stack.pop();
right_path.pop();
dist[u] = UNMATCHED;
continue 'outer;
}
let v = edges[*ei];
*ei += 1;
let w = match_right[v];
let w_idx = if w == UNMATCHED { left_n } else { w };
let du = dist[u];
if du == UNMATCHED {
continue;
}
if dist[w_idx] != du + 1 {
continue;
}
if w_idx == left_n {
right_path.push(v);
let path_len = right_path.len();
for k in 0..path_len {
let lv = call_stack[k].0;
let rv = right_path[k];
match_left[lv] = rv;
match_right[rv] = lv;
}
return true;
}
dist[w_idx] = UNMATCHED;
right_path.push(v);
call_stack.push((w_idx, 0));
continue 'outer;
}
}
false
}
pub fn find_unmatched_left(matching: &[Option<usize>]) -> Vec<usize> {
matching
.iter()
.enumerate()
.filter_map(|(i, m)| if m.is_none() { Some(i) } else { None })
.collect()
}
pub fn alternating_reachable(
left_n: usize,
right_n: usize,
adj: &[Vec<usize>],
matching: &[Option<usize>],
) -> Vec<usize> {
let mut match_right: Vec<Option<usize>> = vec![None; right_n];
for (l, m) in matching.iter().enumerate() {
if let Some(r) = m {
if *r < right_n {
match_right[*r] = Some(l);
}
}
}
let mut visited_left = vec![false; left_n];
let mut visited_right = vec![false; right_n];
let mut queue: VecDeque<usize> = VecDeque::new();
for u in 0..left_n {
if matching[u].is_none() {
visited_left[u] = true;
queue.push_back(u);
}
}
while let Some(u) = queue.pop_front() {
if u < adj.len() {
for &v in &adj[u] {
if v < right_n && !visited_right[v] {
visited_right[v] = true;
if let Some(w) = match_right[v] {
if !visited_left[w] {
visited_left[w] = true;
queue.push_back(w);
}
}
}
}
}
}
(0..left_n).filter(|&i| visited_left[i]).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_perfect_matching_3x3() {
let edges = vec![(0, 0), (1, 1), (2, 2)];
let m = hopcroft_karp(3, 3, &edges);
assert_eq!(m[0], Some(0));
assert_eq!(m[1], Some(1));
assert_eq!(m[2], Some(2));
}
#[test]
fn test_empty_graph() {
let m = hopcroft_karp(3, 3, &[]);
assert!(m.iter().all(|x| x.is_none()));
}
#[test]
fn test_partial_matching() {
let edges = vec![(0, 0), (1, 0), (2, 1)];
let m = hopcroft_karp(3, 2, &edges);
let matched_count = m.iter().filter(|x| x.is_some()).count();
assert_eq!(matched_count, 2);
}
#[test]
fn test_alternating_reachable_simple() {
let edges = vec![(0usize, 0usize), (1, 1)];
let matching = hopcroft_karp(2, 2, &edges);
let adj: Vec<Vec<usize>> = vec![vec![0], vec![1]];
let reachable = alternating_reachable(2, 2, &adj, &matching);
assert!(
reachable.is_empty(),
"No unmatched vertices means nothing reachable: {:?}",
reachable
);
}
#[test]
fn test_alternating_reachable_with_unmatched() {
let edges = vec![(0, 0), (1, 1)];
let matching = hopcroft_karp(3, 2, &edges);
let adj: Vec<Vec<usize>> = vec![vec![0], vec![1], vec![]];
let reachable = alternating_reachable(3, 2, &adj, &matching);
assert!(
reachable.contains(&2),
"Unmatched left-2 must be reachable: {:?}",
reachable
);
}
#[test]
fn test_chain_augmenting() {
let edges = vec![(0, 0), (1, 0), (1, 1), (2, 1), (2, 2)];
let m = hopcroft_karp(3, 3, &edges);
let matched_count = m.iter().filter(|x| x.is_some()).count();
assert_eq!(
matched_count, 3,
"Perfect matching should be found: {:?}",
m
);
let mut rights: Vec<usize> = m.iter().filter_map(|x| *x).collect();
rights.sort_unstable();
rights.dedup();
assert_eq!(rights.len(), 3);
}
}