use std::collections::VecDeque;
use crate::error::{GraphalgError, GraphalgResult};
use crate::repr::weighted_graph::WeightedGraph;
#[derive(Debug, Clone)]
pub struct ZeroOneBfsOutput {
pub dist: Vec<u64>,
pub parent: Vec<usize>,
}
pub fn zero_one_bfs(g: &WeightedGraph, source: usize) -> GraphalgResult<ZeroOneBfsOutput> {
if source >= g.n {
return Err(GraphalgError::SourceOutOfRange {
node: source,
n: g.n,
});
}
for (u, adj) in g.edges.iter().enumerate() {
for &(v, w) in adj {
let is_zero = w.abs() < 1e-9;
let is_one = (w - 1.0).abs() < 1e-9;
if !(is_zero || is_one) {
return Err(GraphalgError::InvalidEdgeWeight(format!(
"edge ({u},{v}) weight {w} is not 0 or 1"
)));
}
}
}
let n = g.n;
let mut dist = vec![u64::MAX; n];
let mut parent = vec![usize::MAX; n];
dist[source] = 0;
parent[source] = source;
let mut dq: VecDeque<usize> = VecDeque::new();
dq.push_front(source);
while let Some(u) = dq.pop_front() {
let du = dist[u];
for &(v, w) in g.neighbors(u)? {
let weight = if w < 0.5 { 0u64 } else { 1u64 };
let nd = du.saturating_add(weight);
if nd < dist[v] {
dist[v] = nd;
parent[v] = u;
if weight == 0 {
dq.push_front(v);
} else {
dq.push_back(v);
}
}
}
}
Ok(ZeroOneBfsOutput { dist, parent })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_ones_is_plain_bfs() {
let mut g = WeightedGraph::new(4);
g.add_edge(0, 1, 1.0).expect("ok");
g.add_edge(1, 2, 1.0).expect("ok");
g.add_edge(2, 3, 1.0).expect("ok");
let out = zero_one_bfs(&g, 0).expect("ok");
assert_eq!(out.dist, vec![0, 1, 2, 3]);
}
#[test]
fn zero_edge_is_free() {
let mut g = WeightedGraph::new(3);
g.add_edge(0, 1, 1.0).expect("ok");
g.add_edge(1, 2, 0.0).expect("ok");
let out = zero_one_bfs(&g, 0).expect("ok");
assert_eq!(out.dist[1], 1);
assert_eq!(out.dist[2], 1, "0-weight edge should be free");
}
#[test]
fn prefers_cheaper_zero_path() {
let mut g = WeightedGraph::new(4);
g.add_edge(0, 3, 1.0).expect("ok");
g.add_edge(0, 1, 0.0).expect("ok");
g.add_edge(1, 2, 0.0).expect("ok");
g.add_edge(2, 3, 0.0).expect("ok");
let out = zero_one_bfs(&g, 0).expect("ok");
assert_eq!(out.dist[3], 0, "should take the all-zero path");
}
#[test]
fn source_distance_zero() {
let mut g = WeightedGraph::new(2);
g.add_edge(0, 1, 1.0).expect("ok");
let out = zero_one_bfs(&g, 0).expect("ok");
assert_eq!(out.dist[0], 0);
assert_eq!(out.parent[0], 0);
}
#[test]
fn unreachable_is_max() {
let mut g = WeightedGraph::new(3);
g.add_edge(0, 1, 1.0).expect("ok");
let out = zero_one_bfs(&g, 0).expect("ok");
assert_eq!(out.dist[2], u64::MAX);
assert_eq!(out.parent[2], usize::MAX);
}
#[test]
fn rejects_non_binary_weight() {
let mut g = WeightedGraph::new(2);
g.add_edge(0, 1, 2.0).expect("ok");
assert!(matches!(
zero_one_bfs(&g, 0).unwrap_err(),
GraphalgError::InvalidEdgeWeight(_)
));
}
#[test]
fn source_out_of_range() {
let g = WeightedGraph::new(3);
assert!(matches!(
zero_one_bfs(&g, 9).unwrap_err(),
GraphalgError::SourceOutOfRange { .. }
));
}
#[test]
fn matches_unit_bfs_on_grid() {
let mut g = WeightedGraph::new(5);
g.add_undirected_edge(0, 1, 1.0).expect("ok");
g.add_undirected_edge(1, 2, 1.0).expect("ok");
g.add_undirected_edge(2, 3, 1.0).expect("ok");
g.add_undirected_edge(3, 4, 1.0).expect("ok");
g.add_undirected_edge(4, 0, 1.0).expect("ok");
let out = zero_one_bfs(&g, 0).expect("ok");
assert_eq!(out.dist, vec![0, 1, 2, 2, 1]);
}
#[test]
fn mixed_weights_correct() {
let mut g = WeightedGraph::new(5);
g.add_edge(0, 1, 0.0).expect("ok");
g.add_edge(1, 2, 1.0).expect("ok");
g.add_edge(2, 3, 0.0).expect("ok");
g.add_edge(3, 4, 1.0).expect("ok");
let out = zero_one_bfs(&g, 0).expect("ok");
assert_eq!(out.dist, vec![0, 0, 1, 1, 2]);
}
#[test]
fn parent_chain_reconstructs_path() {
let mut g = WeightedGraph::new(4);
g.add_edge(0, 1, 1.0).expect("ok");
g.add_edge(1, 2, 1.0).expect("ok");
g.add_edge(2, 3, 1.0).expect("ok");
let out = zero_one_bfs(&g, 0).expect("ok");
let mut path = vec![3usize];
let mut cur = 3usize;
while cur != 0 {
cur = out.parent[cur];
path.push(cur);
}
path.reverse();
assert_eq!(path, vec![0, 1, 2, 3]);
}
#[test]
fn deterministic() {
let mut g = WeightedGraph::new(4);
g.add_edge(0, 1, 0.0).expect("ok");
g.add_edge(0, 2, 1.0).expect("ok");
g.add_edge(1, 3, 1.0).expect("ok");
g.add_edge(2, 3, 0.0).expect("ok");
let a = zero_one_bfs(&g, 0).expect("ok");
let b = zero_one_bfs(&g, 0).expect("ok");
assert_eq!(a.dist, b.dist);
}
#[test]
fn self_loop_ignored() {
let mut g = WeightedGraph::new(2);
g.add_edge(0, 0, 0.0).expect("ok");
g.add_edge(0, 1, 1.0).expect("ok");
let out = zero_one_bfs(&g, 0).expect("ok");
assert_eq!(out.dist[0], 0);
assert_eq!(out.dist[1], 1);
}
}