use std::collections::HashSet;
use crate::error::{GraphalgError, GraphalgResult};
use crate::repr::weighted_graph::WeightedGraph;
#[derive(Debug, Clone)]
pub struct DeltaSteppingOutput {
pub dist: Vec<f64>,
pub parent: Vec<usize>,
}
pub fn delta_stepping(
g: &WeightedGraph,
source: usize,
delta: f64,
) -> GraphalgResult<DeltaSteppingOutput> {
if source >= g.n {
return Err(GraphalgError::SourceOutOfRange {
node: source,
n: g.n,
});
}
if !(delta.is_finite() && delta > 0.0) {
return Err(GraphalgError::InvalidParameter(format!(
"delta must be finite and > 0, got {delta}"
)));
}
for (u, adj) in g.edges.iter().enumerate() {
for &(v, w) in adj {
if w < 0.0 {
return Err(GraphalgError::NegativeWeight {
edge: (u, v),
weight: w,
});
}
}
}
let n = g.n;
let mut tent = vec![f64::INFINITY; n];
let mut parent = vec![usize::MAX; n];
let mut bucket_of = vec![usize::MAX; n];
let mut buckets: Vec<HashSet<usize>> = Vec::new();
fn relax(
v: usize,
x: f64,
p: usize,
delta: f64,
tent: &mut [f64],
parent: &mut [usize],
bucket_of: &mut [usize],
buckets: &mut Vec<HashSet<usize>>,
) {
if x < tent[v] {
let old = bucket_of[v];
if old != usize::MAX {
if let Some(b) = buckets.get_mut(old) {
b.remove(&v);
}
}
let new_idx = (x / delta).floor() as usize;
if new_idx >= buckets.len() {
buckets.resize(new_idx + 1, HashSet::new());
}
buckets[new_idx].insert(v);
bucket_of[v] = new_idx;
tent[v] = x;
parent[v] = p;
}
}
relax(
source,
0.0,
source,
delta,
&mut tent,
&mut parent,
&mut bucket_of,
&mut buckets,
);
let mut i = 0usize;
while i < buckets.len() {
if buckets[i].is_empty() {
i += 1;
continue;
}
let mut settled: Vec<usize> = Vec::new();
while !buckets[i].is_empty() {
let current: Vec<usize> = buckets[i].drain().collect();
for &u in ¤t {
bucket_of[u] = usize::MAX;
settled.push(u);
}
for &u in ¤t {
let du = tent[u];
for &(v, w) in g.neighbors(u)? {
if w <= delta {
relax(
v,
du + w,
u,
delta,
&mut tent,
&mut parent,
&mut bucket_of,
&mut buckets,
);
}
}
}
}
for &u in &settled {
let du = tent[u];
for &(v, w) in g.neighbors(u)? {
if w > delta {
relax(
v,
du + w,
u,
delta,
&mut tent,
&mut parent,
&mut bucket_of,
&mut buckets,
);
}
}
}
if buckets[i].is_empty() {
i += 1;
}
}
Ok(DeltaSteppingOutput { dist: tent, parent })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shortest_path::dijkstra::dijkstra;
#[test]
fn simple_chain() {
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 = delta_stepping(&g, 0, 1.0).expect("ok");
assert!((out.dist[3] - 3.0).abs() < 1e-12);
}
#[test]
fn matches_dijkstra_small() {
let mut g = WeightedGraph::new(4);
g.add_edge(0, 1, 1.0).expect("ok");
g.add_edge(0, 2, 4.0).expect("ok");
g.add_edge(1, 2, 2.0).expect("ok");
g.add_edge(1, 3, 5.0).expect("ok");
g.add_edge(2, 3, 1.0).expect("ok");
let dj = dijkstra(&g, 0).expect("ok");
for &delta in &[0.5_f64, 1.0, 2.0, 10.0] {
let ds = delta_stepping(&g, 0, delta).expect("ok");
for v in 0..g.n {
assert!(
(ds.dist[v] - dj.dist[v]).abs() < 1e-9,
"delta={delta} v={v}: {} vs {}",
ds.dist[v],
dj.dist[v]
);
}
}
}
#[test]
fn matches_dijkstra_undirected_grid() {
let mut g = WeightedGraph::new(6);
g.add_undirected_edge(0, 1, 0.3).expect("ok");
g.add_undirected_edge(1, 2, 2.5).expect("ok");
g.add_undirected_edge(0, 3, 1.0).expect("ok");
g.add_undirected_edge(3, 4, 0.7).expect("ok");
g.add_undirected_edge(4, 5, 3.2).expect("ok");
g.add_undirected_edge(2, 5, 0.4).expect("ok");
g.add_undirected_edge(1, 4, 1.1).expect("ok");
let dj = dijkstra(&g, 0).expect("ok");
let ds = delta_stepping(&g, 0, 1.0).expect("ok");
for v in 0..g.n {
assert!(
(ds.dist[v] - dj.dist[v]).abs() < 1e-9,
"v={v}: {} vs {}",
ds.dist[v],
dj.dist[v]
);
}
}
#[test]
fn source_distance_zero() {
let mut g = WeightedGraph::new(3);
g.add_edge(0, 1, 2.0).expect("ok");
let out = delta_stepping(&g, 0, 1.0).expect("ok");
assert!((out.dist[0] - 0.0).abs() < 1e-12);
assert_eq!(out.parent[0], 0);
}
#[test]
fn unreachable_is_infinity() {
let mut g = WeightedGraph::new(3);
g.add_edge(0, 1, 1.0).expect("ok");
let out = delta_stepping(&g, 0, 1.0).expect("ok");
assert!(out.dist[2].is_infinite());
assert_eq!(out.parent[2], usize::MAX);
}
#[test]
fn rejects_negative_weight() {
let mut g = WeightedGraph::new(2);
g.add_edge(0, 1, -1.0).expect("ok");
assert!(matches!(
delta_stepping(&g, 0, 1.0).unwrap_err(),
GraphalgError::NegativeWeight { .. }
));
}
#[test]
fn rejects_bad_delta() {
let mut g = WeightedGraph::new(2);
g.add_edge(0, 1, 1.0).expect("ok");
assert!(delta_stepping(&g, 0, 0.0).is_err());
assert!(delta_stepping(&g, 0, -1.0).is_err());
assert!(delta_stepping(&g, 0, f64::INFINITY).is_err());
}
#[test]
fn source_out_of_range() {
let g = WeightedGraph::new(3);
assert!(matches!(
delta_stepping(&g, 5, 1.0).unwrap_err(),
GraphalgError::SourceOutOfRange { .. }
));
}
#[test]
fn parent_pointers_form_tree() {
let mut g = WeightedGraph::new(5);
g.add_edge(0, 1, 2.0).expect("ok");
g.add_edge(0, 2, 5.0).expect("ok");
g.add_edge(1, 2, 1.0).expect("ok");
g.add_edge(2, 3, 2.0).expect("ok");
g.add_edge(1, 4, 7.0).expect("ok");
let out = delta_stepping(&g, 0, 1.5).expect("ok");
for v in 0..g.n {
if v == 0 || out.dist[v].is_infinite() {
continue;
}
let p = out.parent[v];
assert!(p != usize::MAX, "v={v} has no parent");
let w = g
.neighbors(p)
.expect("ok")
.iter()
.find(|&&(t, _)| t == v)
.map(|&(_, w)| w)
.expect("parent edge exists");
assert!(
(out.dist[p] + w - out.dist[v]).abs() < 1e-9,
"tree broken at v={v}"
);
}
}
#[test]
fn large_delta_acts_like_bellman_ford() {
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(0, 2, 5.0).expect("ok");
g.add_edge(2, 3, 1.0).expect("ok");
let dj = dijkstra(&g, 0).expect("ok");
let ds = delta_stepping(&g, 0, 1000.0).expect("ok");
for v in 0..g.n {
assert!((ds.dist[v] - dj.dist[v]).abs() < 1e-9, "v={v}");
}
}
#[test]
fn small_delta_acts_like_dijkstra() {
let mut g = WeightedGraph::new(4);
g.add_edge(0, 1, 1.0).expect("ok");
g.add_edge(0, 2, 4.0).expect("ok");
g.add_edge(1, 2, 2.0).expect("ok");
g.add_edge(2, 3, 1.0).expect("ok");
let dj = dijkstra(&g, 0).expect("ok");
let ds = delta_stepping(&g, 0, 0.1).expect("ok");
for v in 0..g.n {
assert!((ds.dist[v] - dj.dist[v]).abs() < 1e-9, "v={v}");
}
}
#[test]
fn deterministic() {
let mut g = WeightedGraph::new(5);
g.add_undirected_edge(0, 1, 1.0).expect("ok");
g.add_undirected_edge(1, 2, 2.0).expect("ok");
g.add_undirected_edge(2, 3, 1.5).expect("ok");
g.add_undirected_edge(3, 4, 0.5).expect("ok");
let a = delta_stepping(&g, 0, 1.0).expect("ok");
let b = delta_stepping(&g, 0, 1.0).expect("ok");
assert_eq!(a.dist, b.dist);
}
}