use crate::exact::bigint::BigInt;
use crate::graph::core::Graph;
use crate::linalg::matrix::Matrix;
use std::cmp::Ordering;
use std::collections::BinaryHeap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NegativeCycle {
pub witness: usize,
}
impl std::fmt::Display for NegativeCycle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "negative cycle reachable at vertex {}", self.witness)
}
}
impl std::error::Error for NegativeCycle {}
#[derive(PartialEq)]
struct MinKey(f64, usize);
impl Eq for MinKey {}
impl Ord for MinKey {
fn cmp(&self, other: &Self) -> Ordering {
other.0.total_cmp(&self.0).then_with(|| other.1.cmp(&self.1))
}
}
impl PartialOrd for MinKey {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn rebuild(prev: &[Option<usize>], s: usize, t: usize) -> Option<Vec<usize>> {
if s == t {
return Some(vec![s]);
}
let mut path = vec![t];
let mut cur = t;
while let Some(p) = prev[cur] {
path.push(p);
cur = p;
if cur == s {
path.reverse();
return Some(path);
}
}
None
}
#[must_use]
pub fn dijkstra(g: &Graph, s: usize) -> (Vec<f64>, Vec<Option<usize>>) {
assert!(
g.edges().iter().all(|&(_, _, w)| w >= 0.0),
"dijkstra needs non-negative weights"
);
let mut dist = vec![f64::INFINITY; g.n];
let mut prev = vec![None; g.n];
let mut heap = BinaryHeap::new();
dist[s] = 0.0;
heap.push(MinKey(0.0, s));
while let Some(MinKey(d, v)) = heap.pop() {
if d > dist[v] {
continue;
}
for &(w, weight) in &g.adj[v] {
let cand = d + weight;
if cand < dist[w] {
dist[w] = cand;
prev[w] = Some(v);
heap.push(MinKey(cand, w));
}
}
}
(dist, prev)
}
#[must_use]
pub fn dijkstra_target(g: &Graph, s: usize, t: usize) -> Option<(f64, Vec<usize>)> {
let (dist, prev) = dijkstra(g, s);
if !dist[t].is_finite() {
return None;
}
Some((dist[t], rebuild(&prev, s, t)?))
}
pub fn bellman_ford(
g: &Graph,
s: usize,
) -> Result<(Vec<f64>, Vec<Option<usize>>), NegativeCycle> {
let mut dist = vec![f64::INFINITY; g.n];
let mut prev = vec![None; g.n];
dist[s] = 0.0;
let arcs = directed_arcs(g);
for _ in 1..g.n.max(1) {
let mut changed = false;
for &(u, v, w) in &arcs {
if dist[u].is_finite() && dist[u] + w < dist[v] {
dist[v] = dist[u] + w;
prev[v] = Some(u);
changed = true;
}
}
if !changed {
break;
}
}
for &(u, v, w) in &arcs {
if dist[u].is_finite() && dist[u] + w < dist[v] {
return Err(NegativeCycle { witness: v });
}
}
Ok((dist, prev))
}
fn directed_arcs(g: &Graph) -> Vec<(usize, usize, f64)> {
let mut out = Vec::new();
for u in 0..g.n {
for &(v, w) in &g.adj[u] {
out.push((u, v, w));
}
}
out
}
#[must_use]
pub fn floyd_warshall(g: &Graph) -> Matrix {
let n = g.n;
let mut d = Matrix::zeros(n, n);
for i in 0..n {
for j in 0..n {
d.set(i, j, if i == j { 0.0 } else { f64::INFINITY });
}
}
for (u, v, w) in directed_arcs(g) {
if w < d.get(u, v) {
d.set(u, v, w);
}
}
for k in 0..n {
for i in 0..n {
let dik = d.get(i, k);
if !dik.is_finite() {
continue;
}
for j in 0..n {
let cand = dik + d.get(k, j);
if cand < d.get(i, j) {
d.set(i, j, cand);
}
}
}
}
d
}
pub fn johnson(g: &Graph) -> Result<Matrix, NegativeCycle> {
let n = g.n;
let mut aug = Graph::new(n + 1, true);
for (u, v, w) in directed_arcs(g) {
aug.add_edge(u, v, w);
}
for v in 0..n {
aug.add_edge(n, v, 0.0);
}
let (h, _) = bellman_ford(&aug, n)?;
let mut rew = Graph::new(n, true);
for (u, v, w) in directed_arcs(g) {
rew.add_edge(u, v, (w + h[u] - h[v]).max(0.0));
}
let mut out = Matrix::zeros(n, n);
for u in 0..n {
let (d, _) = dijkstra(&rew, u);
for v in 0..n {
let real = if d[v].is_finite() {
d[v] - h[u] + h[v]
} else {
f64::INFINITY
};
out.set(u, v, real);
}
}
Ok(out)
}
pub fn a_star(
g: &Graph,
s: usize,
t: usize,
h: &dyn Fn(usize) -> f64,
) -> Option<(f64, Vec<usize>)> {
assert!(
g.edges().iter().all(|&(_, _, w)| w >= 0.0),
"a_star needs non-negative weights"
);
let mut dist = vec![f64::INFINITY; g.n];
let mut prev = vec![None; g.n];
let mut heap = BinaryHeap::new();
dist[s] = 0.0;
heap.push(MinKey(h(s), s));
while let Some(MinKey(f, v)) = heap.pop() {
if v == t {
return Some((dist[t], rebuild(&prev, s, t)?));
}
if f > dist[v] + h(v) {
continue;
}
for &(w, weight) in &g.adj[v] {
let cand = dist[v] + weight;
if cand < dist[w] {
dist[w] = cand;
prev[w] = Some(v);
heap.push(MinKey(cand + h(w), w));
}
}
}
None
}
#[must_use]
pub fn bidirectional_dijkstra(g: &Graph, s: usize, t: usize) -> Option<(f64, Vec<usize>)> {
assert!(
g.edges().iter().all(|&(_, _, w)| w >= 0.0),
"bidirectional_dijkstra needs non-negative weights"
);
if s == t {
return Some((0.0, vec![s]));
}
let rev = g.reverse();
let mut df = vec![f64::INFINITY; g.n];
let mut db = vec![f64::INFINITY; g.n];
let mut pf: Vec<Option<usize>> = vec![None; g.n];
let mut pb: Vec<Option<usize>> = vec![None; g.n];
let mut hf = BinaryHeap::new();
let mut hb = BinaryHeap::new();
df[s] = 0.0;
db[t] = 0.0;
hf.push(MinKey(0.0, s));
hb.push(MinKey(0.0, t));
let mut best = f64::INFINITY;
let mut meet = usize::MAX;
let (mut rf, mut rb) = (0.0f64, 0.0f64);
while !hf.is_empty() || !hb.is_empty() {
if rf + rb >= best {
break;
}
let forward = match (hf.peek(), hb.peek()) {
(Some(a), Some(b)) => a.0 <= b.0,
(Some(_), None) => true,
(None, Some(_)) => false,
(None, None) => break,
};
let (heap, dist, other, prev, adj, radius) = if forward {
(&mut hf, &mut df, &db, &mut pf, &g.adj, &mut rf)
} else {
(&mut hb, &mut db, &df, &mut pb, &rev.adj, &mut rb)
};
let Some(MinKey(d, v)) = heap.pop() else { break };
if d > dist[v] {
continue;
}
*radius = d;
if other[v].is_finite() && d + other[v] < best {
best = d + other[v];
meet = v;
}
for &(w, weight) in &adj[v] {
let cand = d + weight;
if cand < dist[w] {
dist[w] = cand;
prev[w] = Some(v);
heap.push(MinKey(cand, w));
}
}
}
if meet == usize::MAX {
return None;
}
let mut path = rebuild(&pf, s, meet)?;
let back = rebuild(&pb, t, meet)?;
path.extend(back.into_iter().rev().skip(1));
Some((best, path))
}
#[must_use]
pub fn k_shortest_paths_yen(g: &Graph, s: usize, t: usize, k: usize) -> Vec<(f64, Vec<usize>)> {
let mut accepted: Vec<(f64, Vec<usize>)> = Vec::new();
let Some(first) = dijkstra_target(g, s, t) else {
return accepted;
};
accepted.push(first);
let mut candidates: Vec<(f64, Vec<usize>)> = Vec::new();
while accepted.len() < k {
let last = accepted.last().unwrap().1.clone();
for i in 0..last.len().saturating_sub(1) {
let spur = last[i];
let root = &last[..=i];
let mut banned_arcs: Vec<(usize, usize)> = Vec::new();
for (_, p) in &accepted {
if p.len() > i + 1 && p[..=i] == *root {
banned_arcs.push((p[i], p[i + 1]));
}
}
let banned_vertices: Vec<usize> = root[..i].to_vec();
let mut sub = Graph::new(g.n, true);
for (u, v, w) in directed_arcs(g) {
if banned_vertices.contains(&u) || banned_vertices.contains(&v) {
continue;
}
if banned_arcs.contains(&(u, v)) {
continue;
}
sub.add_edge(u, v, w);
}
let Some((spur_cost, spur_path)) = dijkstra_target(&sub, spur, t) else {
continue;
};
let root_cost: f64 = root
.windows(2)
.map(|w| arc_weight(g, w[0], w[1]).unwrap_or(f64::INFINITY))
.sum();
let mut full = root[..i].to_vec();
full.extend(spur_path);
let total = root_cost + spur_cost;
if !accepted.iter().any(|(_, p)| *p == full)
&& !candidates.iter().any(|(_, p)| *p == full)
{
candidates.push((total, full));
}
}
if candidates.is_empty() {
break;
}
candidates.sort_by(|a, b| a.0.total_cmp(&b.0));
accepted.push(candidates.remove(0));
}
accepted
}
fn arc_weight(g: &Graph, u: usize, v: usize) -> Option<f64> {
g.adj[u]
.iter()
.filter(|&&(t, _)| t == v)
.map(|&(_, w)| w)
.min_by(|a: &f64, b: &f64| a.total_cmp(b))
}
#[must_use]
pub fn widest_path(g: &Graph, s: usize, t: usize) -> Option<(f64, Vec<usize>)> {
let mut width = vec![f64::NEG_INFINITY; g.n];
let mut prev: Vec<Option<usize>> = vec![None; g.n];
let mut heap = BinaryHeap::new();
width[s] = f64::INFINITY;
heap.push(MinKey(f64::NEG_INFINITY, s));
while let Some(MinKey(negw, v)) = heap.pop() {
if -negw < width[v] {
continue;
}
if v == t {
break;
}
for &(w, cap) in &g.adj[v] {
let cand = width[v].min(cap);
if cand > width[w] {
width[w] = cand;
prev[w] = Some(v);
heap.push(MinKey(-cand, w));
}
}
}
if width[t] == f64::NEG_INFINITY {
return None;
}
Some((width[t], rebuild(&prev, s, t)?))
}
#[must_use]
pub fn minimax_path(g: &Graph, s: usize, t: usize) -> Option<(f64, Vec<usize>)> {
let mut bottleneck = vec![f64::INFINITY; g.n];
let mut prev: Vec<Option<usize>> = vec![None; g.n];
let mut heap = BinaryHeap::new();
bottleneck[s] = f64::NEG_INFINITY;
heap.push(MinKey(f64::NEG_INFINITY, s));
while let Some(MinKey(d, v)) = heap.pop() {
if d > bottleneck[v] {
continue;
}
if v == t {
break;
}
for &(w, cost) in &g.adj[v] {
let cand = bottleneck[v].max(cost);
if cand < bottleneck[w] {
bottleneck[w] = cand;
prev[w] = Some(v);
heap.push(MinKey(cand, w));
}
}
}
if !bottleneck[t].is_finite() && bottleneck[t] > 0.0 {
return None;
}
if bottleneck[t] == f64::INFINITY {
return None;
}
Some((bottleneck[t].max(0.0), rebuild(&prev, s, t)?))
}
#[must_use]
pub fn dag_shortest(g: &Graph, s: usize) -> Vec<f64> {
dag_extreme(g, s, true)
}
#[must_use]
pub fn dag_longest(g: &Graph, s: usize) -> Vec<f64> {
dag_extreme(g, s, false)
}
fn dag_extreme(g: &Graph, s: usize, shortest: bool) -> Vec<f64> {
let order = g.topological_sort().expect("dag_shortest needs a DAG");
let unreached = if shortest {
f64::INFINITY
} else {
f64::NEG_INFINITY
};
let mut dist = vec![unreached; g.n];
dist[s] = 0.0;
for &v in &order {
if dist[v] == unreached {
continue;
}
for &(w, weight) in &g.adj[v] {
let cand = dist[v] + weight;
let better = if shortest {
cand < dist[w]
} else {
cand > dist[w]
};
if better {
dist[w] = cand;
}
}
}
dist
}
#[must_use]
pub fn count_paths_dag(g: &Graph, s: usize, t: usize) -> BigInt {
let order = g.topological_sort().expect("count_paths_dag needs a DAG");
let mut count = vec![BigInt::zero(); g.n];
count[s] = BigInt::one();
for &v in &order {
if count[v].is_zero() {
continue;
}
let here = count[v].clone();
for &(w, _) in &g.adj[v] {
count[w] = count[w].add(&here);
}
}
count[t].clone()
}
#[must_use]
pub fn transitive_closure(g: &Graph) -> Vec<Vec<bool>> {
let n = g.n;
let mut r = vec![vec![false; n]; n];
for (i, row) in r.iter_mut().enumerate() {
row[i] = true;
}
for (u, v, _) in directed_arcs(g) {
r[u][v] = true;
}
for k in 0..n {
for i in 0..n {
if r[i][k] {
for j in 0..n {
if r[k][j] {
r[i][j] = true;
}
}
}
}
}
r
}
#[must_use]
pub fn minimum_spanning_tree_kruskal(g: &Graph) -> (f64, Vec<(usize, usize)>) {
let mut edges: Vec<(f64, usize, usize)> = g
.edges()
.into_iter()
.filter(|&(u, v, _)| u != v)
.map(|(u, v, w)| (w, u.min(v), u.max(v)))
.collect();
edges.sort_by(|a, b| a.0.total_cmp(&b.0));
let mut ds = crate::discrete::disjoint_set::DisjointSet::new(g.n);
let mut total = 0.0;
let mut chosen = Vec::new();
for (w, u, v) in edges {
if ds.union(u, v) {
total += w;
chosen.push((u, v));
}
}
(total, chosen)
}
#[must_use]
pub fn minimum_spanning_tree_prim(g: &Graph) -> (f64, Vec<(usize, usize)>) {
let mut in_tree = vec![false; g.n];
let mut total = 0.0;
let mut chosen = Vec::new();
for root in 0..g.n {
if in_tree[root] {
continue;
}
let mut heap = BinaryHeap::new();
in_tree[root] = true;
for &(w, weight) in &g.adj[root] {
heap.push((MinKey(weight, w), root));
}
while let Some((MinKey(weight, v), from)) = heap.pop() {
if in_tree[v] {
continue;
}
in_tree[v] = true;
total += weight;
chosen.push((from.min(v), from.max(v)));
for &(w, next) in &g.adj[v] {
if !in_tree[w] {
heap.push((MinKey(next, w), v));
}
}
}
}
(total, chosen)
}
#[must_use]
pub fn minimum_spanning_tree_boruvka(g: &Graph) -> (f64, Vec<(usize, usize)>) {
let edges: Vec<(usize, usize, f64)> = g
.edges()
.into_iter()
.filter(|&(u, v, _)| u != v)
.map(|(u, v, w)| (u.min(v), u.max(v), w))
.collect();
let mut ds = crate::discrete::disjoint_set::DisjointSet::new(g.n);
let mut total = 0.0;
let mut chosen = Vec::new();
loop {
let mut best: Vec<Option<usize>> = vec![None; g.n];
for (i, &(u, v, w)) in edges.iter().enumerate() {
let (a, b) = (ds.find(u), ds.find(v));
if a == b {
continue;
}
for root in [a, b] {
let better = match best[root] {
None => true,
Some(j) => (w, i) < (edges[j].2, j),
};
if better {
best[root] = Some(i);
}
}
}
let mut added = false;
for root in 0..g.n {
if let Some(i) = best[root] {
let (u, v, w) = edges[i];
if ds.union(u, v) {
total += w;
chosen.push((u, v));
added = true;
}
}
}
if !added {
break;
}
}
(total, chosen)
}
#[must_use]
pub fn second_best_mst(g: &Graph) -> Option<(f64, Vec<(usize, usize)>)> {
let (base_cost, tree) = minimum_spanning_tree_kruskal(g);
if tree.len() + 1 != g.n {
return None;
}
let mut t = Graph::new(g.n, false);
for &(u, v) in &tree {
t.add_edge(u, v, arc_weight(g, u, v).unwrap_or(0.0));
}
let mut best: Option<(f64, (usize, usize), (usize, usize))> = None;
for (u, v, w) in g.edges() {
let (a, b) = (u.min(v), u.max(v));
if u == v || tree.contains(&(a, b)) {
continue;
}
let Some(path) = tree_path(&t, u, v) else {
continue;
};
let Some((hu, hv, hw)) = path
.windows(2)
.map(|p| (p[0], p[1], arc_weight(&t, p[0], p[1]).unwrap_or(0.0)))
.max_by(|x, y| x.2.total_cmp(&y.2))
else {
continue;
};
let delta = w - hw;
if best.as_ref().is_none_or(|(d, _, _)| delta < *d) {
best = Some((delta, (hu.min(hv), hu.max(hv)), (a, b)));
}
}
let (delta, drop, add) = best?;
let mut edges: Vec<(usize, usize)> = tree.into_iter().filter(|&e| e != drop).collect();
edges.push(add);
edges.sort_unstable();
Some((base_cost + delta, edges))
}
fn tree_path(t: &Graph, s: usize, e: usize) -> Option<Vec<usize>> {
let mut prev: Vec<Option<usize>> = vec![None; t.n];
let mut seen = vec![false; t.n];
seen[s] = true;
let mut queue = std::collections::VecDeque::from(vec![s]);
while let Some(v) = queue.pop_front() {
for &(w, _) in &t.adj[v] {
if !seen[w] {
seen[w] = true;
prev[w] = Some(v);
queue.push_back(w);
}
}
}
rebuild(&prev, s, e)
}
#[must_use]
pub fn steiner_tree_small(g: &Graph, terminals: &[usize]) -> (f64, Vec<(usize, usize)>) {
assert!(terminals.len() <= 12, "steiner_tree_small needs at most 12 terminals");
assert!(terminals.iter().all(|&t| t < g.n), "terminal out of range");
let t = terminals.len();
if t <= 1 {
return (0.0, Vec::new());
}
let apsp = floyd_warshall(g);
let full = 1usize << t;
let mut dp = vec![vec![f64::INFINITY; g.n]; full];
for (i, &term) in terminals.iter().enumerate() {
for v in 0..g.n {
dp[1 << i][v] = apsp.get(term, v);
}
}
for mask in 1..full {
if mask.count_ones() < 2 {
continue;
}
for v in 0..g.n {
let mut sub = (mask - 1) & mask;
while sub > 0 {
let other = mask ^ sub;
if sub < other {
let cand = dp[sub][v] + dp[other][v];
if cand < dp[mask][v] {
dp[mask][v] = cand;
}
}
sub = (sub - 1) & mask;
}
}
for v in 0..g.n {
for u in 0..g.n {
let cand = dp[mask][u] + apsp.get(u, v);
if cand < dp[mask][v] {
dp[mask][v] = cand;
}
}
}
}
let cost = (0..g.n).fold(f64::INFINITY, |a, v| a.min(dp[full - 1][v]));
if !cost.is_finite() {
return (f64::INFINITY, Vec::new());
}
let mut edges = Vec::new();
let mut closure = Graph::new(t, false);
for i in 0..t {
for j in i + 1..t {
closure.add_edge(i, j, apsp.get(terminals[i], terminals[j]));
}
}
let (_, mst) = minimum_spanning_tree_kruskal(&closure);
for (i, j) in mst {
if let Some(p) = shortest_path_edges(g, terminals[i], terminals[j]) {
edges.extend(p);
}
}
edges.sort_unstable();
edges.dedup();
(cost, edges)
}
fn shortest_path_edges(g: &Graph, s: usize, t: usize) -> Option<Vec<(usize, usize)>> {
let (_, path) = dijkstra_target(g, s, t)?;
Some(
path.windows(2)
.map(|w| (w[0].min(w[1]), w[0].max(w[1])))
.collect(),
)
}
#[must_use]
pub fn traveling_salesman_exact(dist: &Matrix) -> (f64, Vec<usize>) {
assert_eq!(dist.rows, dist.cols, "the distance matrix must be square");
assert!(dist.rows <= 20, "Held-Karp needs at most 20 cities");
let n = dist.rows;
if n <= 1 {
return (0.0, (0..n).collect());
}
let sub = 1usize << (n - 1);
let mut dp = vec![vec![f64::INFINITY; n - 1]; sub];
let mut parent = vec![vec![usize::MAX; n - 1]; sub];
for j in 0..n - 1 {
dp[1 << j][j] = dist.get(0, j + 1);
}
for mask in 1..sub {
for j in 0..n - 1 {
if mask >> j & 1 == 0 || !dp[mask][j].is_finite() {
continue;
}
let base = dp[mask][j];
for k in 0..n - 1 {
if mask >> k & 1 == 1 {
continue;
}
let cand = base + dist.get(j + 1, k + 1);
let next = mask | 1 << k;
if cand < dp[next][k] {
dp[next][k] = cand;
parent[next][k] = j;
}
}
}
}
let full = sub - 1;
let mut best = f64::INFINITY;
let mut last = 0usize;
for j in 0..n - 1 {
let cand = dp[full][j] + dist.get(j + 1, 0);
if cand < best {
best = cand;
last = j;
}
}
let mut tour = Vec::with_capacity(n);
let mut mask = full;
let mut j = last;
while j != usize::MAX {
tour.push(j + 1);
let p = parent[mask][j];
mask ^= 1 << j;
j = p;
}
tour.push(0);
tour.reverse();
(best, tour)
}
#[must_use]
pub fn tsp_nearest_neighbor(dist: &Matrix) -> (f64, Vec<usize>) {
assert_eq!(dist.rows, dist.cols, "the distance matrix must be square");
let n = dist.rows;
if n == 0 {
return (0.0, Vec::new());
}
let mut seen = vec![false; n];
let mut tour = vec![0usize];
seen[0] = true;
let mut total = 0.0;
let mut cur = 0usize;
for _ in 1..n {
let mut best = f64::INFINITY;
let mut pick = usize::MAX;
for v in 0..n {
if !seen[v] && dist.get(cur, v) < best {
best = dist.get(cur, v);
pick = v;
}
}
seen[pick] = true;
tour.push(pick);
total += best;
cur = pick;
}
total += dist.get(cur, 0);
(total, tour)
}
#[must_use]
pub fn tour_length(dist: &Matrix, tour: &[usize]) -> f64 {
if tour.len() < 2 {
return 0.0;
}
let mut total = 0.0;
for i in 0..tour.len() {
total += dist.get(tour[i], tour[(i + 1) % tour.len()]);
}
total
}
#[must_use]
pub fn tsp_2opt(dist: &Matrix, tour: &[usize]) -> (f64, Vec<usize>) {
assert_eq!(dist.rows, dist.cols, "the distance matrix must be square");
assert!(
crate::discrete::combinatorics::is_permutation(tour) && tour.len() == dist.rows,
"the tour must be a permutation of the cities"
);
let n = tour.len();
let mut t = tour.to_vec();
if n < 4 {
return (tour_length(dist, &t), t);
}
loop {
let mut improved = false;
for i in 0..n - 1 {
for j in i + 2..n {
if i == 0 && j == n - 1 {
continue;
}
let (a, b) = (t[i], t[i + 1]);
let (c, d) = (t[j], t[(j + 1) % n]);
let delta = dist.get(a, c) + dist.get(b, d) - dist.get(a, b) - dist.get(c, d);
if delta < -1e-12 {
t[i + 1..=j].reverse();
improved = true;
}
}
}
if !improved {
break;
}
}
(tour_length(dist, &t), t)
}
#[must_use]
pub fn tsp_or_opt(dist: &Matrix, tour: &[usize]) -> (f64, Vec<usize>) {
assert_eq!(dist.rows, dist.cols, "the distance matrix must be square");
assert!(
crate::discrete::combinatorics::is_permutation(tour) && tour.len() == dist.rows,
"the tour must be a permutation of the cities"
);
let n = tour.len();
let mut t = tour.to_vec();
if n < 5 {
return (tour_length(dist, &t), t);
}
let mut best = tour_length(dist, &t);
loop {
let mut improved = false;
'outer: for len in 1..=3usize {
for start in 0..n {
if start + len > n {
continue;
}
let segment: Vec<usize> = t[start..start + len].to_vec();
let mut rest: Vec<usize> = t.clone();
rest.drain(start..start + len);
for pos in 0..=rest.len() {
for reversed in [false, true] {
let mut cand = rest.clone();
let mut seg = segment.clone();
if reversed {
seg.reverse();
}
for (k, v) in seg.into_iter().enumerate() {
cand.insert(pos + k, v);
}
let len_c = tour_length(dist, &cand);
if len_c < best - 1e-12 {
best = len_c;
t = cand;
improved = true;
break 'outer;
}
}
}
}
}
if !improved {
break;
}
}
(best, t)
}
#[must_use]
pub fn tsp_christofides(dist: &Matrix) -> Option<(f64, Vec<usize>)> {
assert_eq!(dist.rows, dist.cols, "the distance matrix must be square");
let n = dist.rows;
assert!(
(0..n).all(|i| (0..n).all(|j| (dist.get(i, j) - dist.get(j, i)).abs() < 1e-12)),
"Christofides needs a symmetric distance matrix"
);
if n <= 2 {
return Some((tour_length(dist, &(0..n).collect::<Vec<_>>()), (0..n).collect()));
}
let mut g = Graph::new(n, false);
for i in 0..n {
for j in i + 1..n {
g.add_edge(i, j, dist.get(i, j));
}
}
let (_, mst) = minimum_spanning_tree_kruskal(&g);
let mut deg = vec![0usize; n];
for &(u, v) in &mst {
deg[u] += 1;
deg[v] += 1;
}
let odd: Vec<usize> = (0..n).filter(|&v| !deg[v].is_multiple_of(2)).collect();
if odd.len() > 16 {
return None;
}
let matching = min_weight_perfect_matching_brute(dist, &odd);
let mut multi = Graph::new(n, false);
for &(u, v) in &mst {
multi.add_edge(u, v, dist.get(u, v));
}
for &(u, v) in &matching {
multi.add_edge(u, v, dist.get(u, v));
}
let circuit = multi.eulerian_circuit()?;
let mut seen = vec![false; n];
let mut tour = Vec::with_capacity(n);
for v in circuit {
if !seen[v] {
seen[v] = true;
tour.push(v);
}
}
Some((tour_length(dist, &tour), tour))
}
fn min_weight_perfect_matching_brute(dist: &Matrix, vs: &[usize]) -> Vec<(usize, usize)> {
let k = vs.len();
if k == 0 {
return Vec::new();
}
let full = 1usize << k;
let mut dp = vec![f64::INFINITY; full];
let mut choice = vec![(usize::MAX, usize::MAX); full];
dp[0] = 0.0;
for mask in 0..full {
if !dp[mask].is_finite() {
continue;
}
let Some(i) = (0..k).find(|&i| mask >> i & 1 == 0) else {
continue;
};
for j in i + 1..k {
if mask >> j & 1 == 1 {
continue;
}
let next = mask | 1 << i | 1 << j;
let cand = dp[mask] + dist.get(vs[i], vs[j]);
if cand < dp[next] {
dp[next] = cand;
choice[next] = (i, j);
}
}
}
let mut out = Vec::new();
let mut mask = full - 1;
while mask != 0 {
let (i, j) = choice[mask];
if i == usize::MAX {
break;
}
out.push((vs[i].min(vs[j]), vs[i].max(vs[j])));
mask ^= 1 << i | 1 << j;
}
out
}
#[must_use]
pub fn chinese_postman(g: &Graph) -> Option<(f64, Vec<usize>)> {
assert!(!g.directed, "chinese_postman here is for undirected graphs");
let total: f64 = g.edges().iter().map(|&(_, _, w)| w).sum();
if g.edge_count() == 0 {
return Some((0.0, vec![0]));
}
if !g.is_connected() {
return None;
}
let odd: Vec<usize> = (0..g.n).filter(|&v| !g.degree(v).is_multiple_of(2)).collect();
if odd.is_empty() {
let circuit = g.eulerian_circuit()?;
return Some((total, circuit));
}
if odd.len() > 16 {
return None;
}
let apsp = floyd_warshall(g);
let matching = min_weight_perfect_matching_brute(&apsp, &odd);
let extra: f64 = matching.iter().map(|&(u, v)| apsp.get(u, v)).sum();
let mut aug = g.clone();
for &(u, v) in &matching {
if let Some(edges) = shortest_path_edges(g, u, v) {
for (a, b) in edges {
aug.add_edge(a, b, arc_weight(g, a, b).unwrap_or(0.0));
}
}
}
let circuit = aug.eulerian_circuit()?;
Some((total + extra, circuit))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::core::{
complete_graph, cycle_graph, grid_2d, path_graph, petersen_graph, star_graph,
};
use crate::monte_carlo::Rng;
fn random_weighted(n: usize, p: f64, directed: bool, rng: &mut Rng) -> Graph {
let mut g = Graph::new(n, directed);
for u in 0..n {
let start = if directed { 0 } else { u + 1 };
for v in start..n {
if u != v && rng.next_f64() < p {
g.add_edge(u, v, 1.0 + 9.0 * rng.next_f64());
}
}
}
g
}
fn close(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-9 * a.abs().max(b.abs()).max(1.0) || (!a.is_finite() && !b.is_finite())
}
fn path_weight(g: &Graph, path: &[usize]) -> f64 {
path.windows(2)
.map(|w| arc_weight(g, w[0], w[1]).unwrap_or(f64::INFINITY))
.sum()
}
#[test]
fn all_shortest_path_algorithms_agree() {
let mut rng = Rng::new(0x5EED);
for directed in [false, true] {
for n in 1..=9usize {
for _ in 0..15 {
let g = random_weighted(n, 0.4, directed, &mut rng);
let fw = floyd_warshall(&g);
let jn = johnson(&g).expect("no negative weights here");
for s in 0..n {
let (dj, prev) = dijkstra(&g, s);
let (bf, _) = bellman_ford(&g, s).expect("no negative cycle");
for t in 0..n {
assert!(close(dj[t], bf[t]), "dijkstra vs bellman-ford {s}->{t}");
assert!(close(dj[t], fw.get(s, t)), "dijkstra vs floyd {s}->{t}");
assert!(close(dj[t], jn.get(s, t)), "dijkstra vs johnson {s}->{t}");
if dj[t].is_finite() {
let path = rebuild(&prev, s, t).expect("a path exists");
assert_eq!(path[0], s);
assert_eq!(*path.last().unwrap(), t);
assert!(
close(path_weight(&g, &path), dj[t]),
"path weight disagrees at {s}->{t}"
);
}
}
}
}
}
}
}
#[test]
fn bellman_ford_handles_negative_weights_and_cycles() {
let g = Graph::from_edges(
4,
&[(0, 1, 1.0), (0, 2, 5.0), (1, 3, 4.0), (3, 2, -3.0)],
true,
);
let (d, _) = bellman_ford(&g, 0).expect("no cycle");
assert!(close(d[2], 2.0), "expected 1 + 4 - 3 = 2, got {}", d[2]);
assert!(close(d[3], 5.0));
let j = johnson(&g).expect("no cycle");
assert!(close(j.get(0, 2), 2.0));
assert!(close(j.get(0, 3), 5.0));
let f = floyd_warshall(&g);
assert!(close(f.get(0, 2), 2.0));
let bad = Graph::from_edges(3, &[(0, 1, 1.0), (1, 2, -3.0), (2, 0, 1.0)], true);
assert!(bellman_ford(&bad, 0).is_err());
assert!(johnson(&bad).is_err());
let mut split = Graph::new(5, true);
split.add_edge(0, 1, 1.0);
for (u, v, w) in [(2, 3, 1.0), (3, 4, -3.0), (4, 2, 1.0)] {
split.add_edge(u, v, w);
}
assert!(
bellman_ford(&split, 0).is_ok(),
"the cycle is unreachable from 0"
);
assert!(johnson(&split).is_err());
}
#[test]
fn a_star_matches_dijkstra_with_admissible_heuristics() {
let mut rng = Rng::new(0xA57A2);
for n in 2..=9usize {
for _ in 0..15 {
let g = random_weighted(n, 0.5, false, &mut rng);
for s in 0..n {
let (d, _) = dijkstra(&g, s);
for t in 0..n {
match a_star(&g, s, t, &|_| 0.0) {
Some((len, path)) => {
assert!(close(len, d[t]), "A* {s}->{t}");
assert!(close(path_weight(&g, &path), len));
}
None => assert!(!d[t].is_finite()),
}
let (dt, _) = dijkstra(&g.reverse(), t);
let perfect =
a_star(&g, s, t, &|v| if dt[v].is_finite() { dt[v] } else { 0.0 });
match perfect {
Some((len, _)) => assert!(close(len, d[t]), "perfect h {s}->{t}"),
None => assert!(!d[t].is_finite()),
}
}
}
}
}
let w = 6usize;
let g = grid_2d(w, 6);
let h = |v: usize| {
let (x, y) = (v % w, v / w);
let (tx, ty) = (35 % w, 35 / w);
(x as f64 - tx as f64).abs() + (y as f64 - ty as f64).abs()
};
let (len, path) = a_star(&g, 0, 35, &h).expect("the grid is connected");
assert!(close(len, 10.0), "Manhattan distance from a corner is 10");
assert_eq!(path.len(), 11);
}
#[test]
fn bidirectional_dijkstra_matches_dijkstra() {
let mut rng = Rng::new(0xB1D1);
for directed in [false, true] {
for n in 1..=9usize {
for _ in 0..15 {
let g = random_weighted(n, 0.45, directed, &mut rng);
for s in 0..n {
let (d, _) = dijkstra(&g, s);
for t in 0..n {
match bidirectional_dijkstra(&g, s, t) {
Some((len, path)) => {
assert!(
close(len, d[t]),
"n={n} {s}->{t}: {len} vs {}",
d[t]
);
assert_eq!(path[0], s);
assert_eq!(*path.last().unwrap(), t);
assert!(
close(path_weight(&g, &path), len),
"spliced path weight disagrees"
);
}
None => {
assert!(!d[t].is_finite(), "missed a reachable {s}->{t}")
}
}
}
}
}
}
}
}
#[test]
fn yen_returns_increasing_distinct_loopless_paths() {
let mut rng = Rng::new(0x7E71);
for n in 2..=8usize {
for _ in 0..10 {
let g = random_weighted(n, 0.5, true, &mut rng);
for s in 0..n {
for t in 0..n {
if s == t {
continue;
}
let ks = k_shortest_paths_yen(&g, s, t, 4);
let best = dijkstra_target(&g, s, t);
match (&best, ks.first()) {
(Some((bl, _)), Some((kl, _))) => assert!(close(*bl, *kl)),
(None, None) => {}
_ => panic!("Yen and Dijkstra disagree on reachability"),
}
for (len, path) in &ks {
assert_eq!(path[0], s);
assert_eq!(*path.last().unwrap(), t);
assert!(
close(path_weight(&g, path), *len),
"claimed length is wrong"
);
let mut sorted = path.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), path.len(), "path repeats a vertex");
}
for w in ks.windows(2) {
assert!(w[0].0 <= w[1].0 + 1e-12, "not in increasing order");
assert_ne!(w[0].1, w[1].1);
}
}
}
}
}
let g = Graph::from_edges(
4,
&[
(0, 1, 1.0),
(0, 2, 2.0),
(1, 3, 5.0),
(2, 3, 3.0),
(0, 3, 9.0),
],
true,
);
let ks = k_shortest_paths_yen(&g, 0, 3, 5);
assert_eq!(ks.len(), 3);
assert!(close(ks[0].0, 5.0), "0-2-3 costs 5");
assert!(close(ks[1].0, 6.0), "0-1-3 costs 6");
assert!(close(ks[2].0, 9.0), "the direct arc costs 9");
}
#[test]
fn widest_and_minimax_paths_match_brute_force() {
let mut rng = Rng::new(0x21DE);
for n in 2..=7usize {
for _ in 0..15 {
let g = random_weighted(n, 0.5, false, &mut rng);
for s in 0..n {
for t in 0..n {
if s == t {
continue;
}
let paths = all_simple_paths(&g, s, t);
let widest = paths
.iter()
.map(|p| {
p.windows(2)
.map(|w| arc_weight(&g, w[0], w[1]).unwrap())
.fold(f64::INFINITY, f64::min)
})
.fold(f64::NEG_INFINITY, f64::max);
let narrowest = paths
.iter()
.map(|p| {
p.windows(2)
.map(|w| arc_weight(&g, w[0], w[1]).unwrap())
.fold(f64::NEG_INFINITY, f64::max)
})
.fold(f64::INFINITY, f64::min);
match widest_path(&g, s, t) {
Some((w, path)) => {
assert!(close(w, widest), "widest {s}->{t}: {w} vs {widest}");
let actual = path
.windows(2)
.map(|x| arc_weight(&g, x[0], x[1]).unwrap())
.fold(f64::INFINITY, f64::min);
assert!(close(actual, w), "reported path is not that wide");
}
None => assert!(paths.is_empty()),
}
match minimax_path(&g, s, t) {
Some((w, path)) => {
assert!(close(w, narrowest), "minimax {s}->{t}");
let actual = path
.windows(2)
.map(|x| arc_weight(&g, x[0], x[1]).unwrap())
.fold(f64::NEG_INFINITY, f64::max);
assert!(close(actual, w));
}
None => assert!(paths.is_empty()),
}
}
}
}
}
}
fn all_simple_paths(g: &Graph, s: usize, t: usize) -> Vec<Vec<usize>> {
fn go(
g: &Graph,
cur: usize,
t: usize,
on_path: &mut Vec<bool>,
path: &mut Vec<usize>,
out: &mut Vec<Vec<usize>>,
) {
if cur == t {
out.push(path.clone());
return;
}
for &(w, _) in &g.adj[cur] {
if !on_path[w] {
on_path[w] = true;
path.push(w);
go(g, w, t, on_path, path, out);
path.pop();
on_path[w] = false;
}
}
}
let mut on_path = vec![false; g.n];
on_path[s] = true;
let mut path = vec![s];
let mut out = Vec::new();
go(g, s, t, &mut on_path, &mut path, &mut out);
out
}
#[test]
fn minimax_path_matches_the_mst_path() {
let mut rng = Rng::new(0x1157);
for n in 2..=8usize {
for _ in 0..15 {
let mut g = Graph::new(n, false);
for u in 0..n {
for v in u + 1..n {
g.add_edge(u, v, 1.0 + 9.0 * rng.next_f64());
}
}
let (_, mst) = minimum_spanning_tree_kruskal(&g);
let mut t = Graph::new(n, false);
for &(u, v) in &mst {
t.add_edge(u, v, arc_weight(&g, u, v).unwrap());
}
for s in 0..n {
for e in 0..n {
if s == e {
continue;
}
let (bottleneck, _) = minimax_path(&g, s, e).expect("connected");
let tp = tree_path(&t, s, e).expect("the tree is connected");
let on_tree = tp
.windows(2)
.map(|w| arc_weight(&t, w[0], w[1]).unwrap())
.fold(f64::NEG_INFINITY, f64::max);
assert!(
close(bottleneck, on_tree),
"n = {n}, {s}->{e}: {bottleneck} vs {on_tree}"
);
}
}
}
}
}
fn grid_dag(side: usize) -> Graph {
let mut g = Graph::new(side * side, true);
for y in 0..side {
for x in 0..side {
let v = y * side + x;
if x + 1 < side {
g.add_edge(v, v + 1, 1.0);
}
if y + 1 < side {
g.add_edge(v, v + side, 1.0);
}
}
}
g
}
#[test]
fn dag_distances_and_path_counts_are_exact() {
for n in 1..=9usize {
let side = n + 1;
let g = grid_dag(side);
assert!(g.is_dag());
let paths = count_paths_dag(&g, 0, side * side - 1);
assert_eq!(paths, BigInt::binomial(2 * n as u64, n as u64), "n = {n}");
let s = dag_shortest(&g, 0);
let l = dag_longest(&g, 0);
assert!(close(s[side * side - 1], 2.0 * n as f64));
assert!(close(l[side * side - 1], 2.0 * n as f64));
}
let big = grid_dag(35);
let c = count_paths_dag(&big, 0, 35 * 35 - 1);
assert_eq!(c, BigInt::binomial(68, 34));
assert!(c > BigInt::from_str_radix(&u64::MAX.to_string(), 10).unwrap());
let g = Graph::from_edges(
4,
&[(0, 1, 1.0), (0, 2, 5.0), (1, 3, 1.0), (2, 3, 1.0)],
true,
);
assert!(close(dag_shortest(&g, 0)[3], 2.0));
assert!(close(dag_longest(&g, 0)[3], 6.0));
let neg = Graph::from_edges(
4,
&[(0, 1, 1.0), (0, 2, 5.0), (1, 3, -4.0), (2, 3, 1.0)],
true,
);
let (bf, _) = bellman_ford(&neg, 0).unwrap();
let ds = dag_shortest(&neg, 0);
for v in 0..4 {
assert!(close(ds[v], bf[v]), "vertex {v}");
}
}
#[test]
fn transitive_closure_matches_reachability() {
let mut rng = Rng::new(0xC105);
for directed in [false, true] {
for n in 1..=9usize {
let g = random_weighted(n, 0.25, directed, &mut rng);
let r = transitive_closure(&g);
for s in 0..n {
let bfs = g.bfs(s);
for t in 0..n {
assert_eq!(r[s][t], bfs[t].is_some(), "({s}, {t})");
}
}
}
}
}
#[test]
fn all_mst_algorithms_agree() {
let mut rng = Rng::new(0x5A7);
for n in 1..=10usize {
for _ in 0..20 {
let g = random_weighted(n, 0.4, false, &mut rng);
let (wk, ek) = minimum_spanning_tree_kruskal(&g);
let (wp, ep) = minimum_spanning_tree_prim(&g);
let (wb, eb) = minimum_spanning_tree_boruvka(&g);
assert!(close(wk, wp), "kruskal {wk} vs prim {wp} at n = {n}");
assert!(close(wk, wb), "kruskal {wk} vs boruvka {wb} at n = {n}");
let components = g.connected_components().len();
for (name, edges) in [("kruskal", &ek), ("prim", &ep), ("boruvka", &eb)] {
assert_eq!(edges.len(), n - components, "{name} edge count");
let mut ds = crate::discrete::disjoint_set::DisjointSet::new(n);
for &(u, v) in edges {
assert!(ds.union(u, v), "{name} produced a cycle");
}
assert_eq!(ds.count(), components, "{name} does not span");
}
if n <= 6 && components == 1 {
let best = brute_force_mst_weight(&g);
assert!(close(wk, best), "n = {n}: {wk} vs brute force {best}");
}
let sum: f64 = ek.iter().map(|&(u, v)| arc_weight(&g, u, v).unwrap()).sum();
assert!(close(wk, sum));
}
}
}
fn brute_force_mst_weight(g: &Graph) -> f64 {
let edges: Vec<(usize, usize, f64)> =
g.edges().into_iter().filter(|&(u, v, _)| u != v).collect();
let mut best = f64::INFINITY;
for combo in crate::discrete::combinatorics::combinations_iter(edges.len(), g.n - 1) {
let mut ds = crate::discrete::disjoint_set::DisjointSet::new(g.n);
let mut ok = true;
let mut total = 0.0;
for &i in &combo {
let (u, v, w) = edges[i];
if !ds.union(u, v) {
ok = false;
break;
}
total += w;
}
if ok && ds.count() == 1 {
best = best.min(total);
}
}
best
}
#[test]
fn second_best_mst_is_the_next_cheapest_tree() {
let mut rng = Rng::new(0x2D0);
for n in 3..=6usize {
for _ in 0..25 {
let mut g = Graph::new(n, false);
for u in 0..n {
for v in u + 1..n {
g.add_edge(u, v, 1.0 + 9.0 * rng.next_f64());
}
}
let (best, tree) = minimum_spanning_tree_kruskal(&g);
let (second, other) =
second_best_mst(&g).expect("a complete graph has a second tree");
assert!(second >= best - 1e-9, "second {second} is below best {best}");
let mut t1 = tree.clone();
t1.sort_unstable();
assert_ne!(t1, other, "the second tree is the same tree");
assert_eq!(other.len(), n - 1);
let mut ds = crate::discrete::disjoint_set::DisjointSet::new(n);
let mut total = 0.0;
for &(u, v) in &other {
assert!(ds.union(u, v));
total += arc_weight(&g, u, v).unwrap();
}
assert_eq!(ds.count(), 1);
assert!(close(total, second), "reported weight is wrong");
let cheapest_other = brute_force_second_best(&g, &t1);
assert!(
close(second, cheapest_other),
"n = {n}: {second} vs {cheapest_other}"
);
}
}
assert!(second_best_mst(&path_graph(5)).is_none());
assert!(second_best_mst(&Graph::new(4, false)).is_none());
}
fn brute_force_second_best(g: &Graph, best_tree: &[(usize, usize)]) -> f64 {
let edges: Vec<(usize, usize, f64)> = g.edges();
let mut best = f64::INFINITY;
for combo in crate::discrete::combinatorics::combinations_iter(edges.len(), g.n - 1) {
let mut ds = crate::discrete::disjoint_set::DisjointSet::new(g.n);
let mut ok = true;
let mut total = 0.0;
let mut set: Vec<(usize, usize)> = Vec::new();
for &i in &combo {
let (u, v, w) = edges[i];
if !ds.union(u, v) {
ok = false;
break;
}
total += w;
set.push((u.min(v), u.max(v)));
}
set.sort_unstable();
if ok && ds.count() == 1 && set != best_tree {
best = best.min(total);
}
}
best
}
#[test]
fn steiner_tree_matches_brute_force() {
let mut rng = Rng::new(0x57E1);
for n in 3..=7usize {
for _ in 0..10 {
let mut g = Graph::new(n, false);
for u in 0..n {
for v in u + 1..n {
g.add_edge(u, v, 1.0 + 9.0 * rng.next_f64());
}
}
for t in 2..=n.min(4) {
let terminals: Vec<usize> = (0..t).collect();
let (cost, edges) = steiner_tree_small(&g, &terminals);
let brute = brute_steiner(&g, &terminals);
assert!(close(cost, brute), "n = {n}, t = {t}: {cost} vs {brute}");
let mut ds = crate::discrete::disjoint_set::DisjointSet::new(n);
for &(u, v) in &edges {
ds.union(u, v);
}
for &term in &terminals {
assert!(
ds.connected(terminals[0], term),
"terminal {term} is cut off"
);
}
}
}
}
assert_eq!(steiner_tree_small(&complete_graph(5), &[]).0, 0.0);
assert_eq!(steiner_tree_small(&complete_graph(5), &[2]).0, 0.0);
}
fn brute_steiner(g: &Graph, terminals: &[usize]) -> f64 {
let others: Vec<usize> = (0..g.n).filter(|v| !terminals.contains(v)).collect();
let mut best = f64::INFINITY;
for extra in 0..=others.len() {
for combo in crate::discrete::combinatorics::combinations_iter(others.len(), extra) {
let mut vs: Vec<usize> = terminals.to_vec();
vs.extend(combo.iter().map(|&i| others[i]));
vs.sort_unstable();
let sub = g.subgraph(&vs);
if !sub.is_connected() {
continue;
}
best = best.min(minimum_spanning_tree_kruskal(&sub).0);
}
}
best
}
fn random_metric(n: usize, rng: &mut Rng) -> Matrix {
let pts: Vec<(f64, f64)> = (0..n).map(|_| (rng.next_f64(), rng.next_f64())).collect();
let mut m = Matrix::zeros(n, n);
for i in 0..n {
for j in 0..n {
let (dx, dy) = (pts[i].0 - pts[j].0, pts[i].1 - pts[j].1);
m.set(i, j, (dx * dx + dy * dy).sqrt());
}
}
m
}
#[test]
fn held_karp_matches_brute_force() {
let mut rng = Rng::new(0x7595);
for n in 1..=8usize {
for _ in 0..10 {
let d = random_metric(n, &mut rng);
let (cost, tour) = traveling_salesman_exact(&d);
assert_eq!(tour.len(), n);
assert!(crate::discrete::combinatorics::is_permutation(&tour));
if n > 1 {
assert_eq!(tour[0], 0, "the tour must start at 0");
}
assert!(close(cost, tour_length(&d, &tour)), "claimed cost is wrong");
if n >= 2 {
let rest: Vec<usize> = (1..n).collect();
let best = crate::discrete::combinatorics::permutations_iter(&rest)
.map(|p| {
let mut t = vec![0usize];
t.extend(p);
tour_length(&d, &t)
})
.fold(f64::INFINITY, f64::min);
assert!(close(cost, best), "n = {n}: {cost} vs {best}");
}
}
}
let mut d = Matrix::zeros(5, 5);
for i in 0..5 {
for j in 0..5 {
d.set(i, j, if i == j { 0.0 } else { 1.0 });
}
}
assert!(close(traveling_salesman_exact(&d).0, 5.0));
}
#[test]
fn tsp_heuristics_only_improve() {
let mut rng = Rng::new(0x2077);
for n in 5..=9usize {
for _ in 0..8 {
let d = random_metric(n, &mut rng);
let (nn_cost, nn_tour) = tsp_nearest_neighbor(&d);
assert!(crate::discrete::combinatorics::is_permutation(&nn_tour));
assert!(close(nn_cost, tour_length(&d, &nn_tour)));
let (two_cost, two_tour) = tsp_2opt(&d, &nn_tour);
assert!(crate::discrete::combinatorics::is_permutation(&two_tour));
assert!(close(two_cost, tour_length(&d, &two_tour)));
assert!(two_cost <= nn_cost + 1e-9, "2-opt made it worse");
let (or_cost, or_tour) = tsp_or_opt(&d, &two_tour);
assert!(crate::discrete::combinatorics::is_permutation(&or_tour));
assert!(close(or_cost, tour_length(&d, &or_tour)));
assert!(or_cost <= two_cost + 1e-9, "or-opt made it worse");
let (opt, _) = traveling_salesman_exact(&d);
assert!(or_cost >= opt - 1e-9, "a heuristic beat the optimum");
for i in 0..n - 1 {
for j in i + 2..n {
if i == 0 && j == n - 1 {
continue;
}
let mut t = two_tour.clone();
t[i + 1..=j].reverse();
assert!(
tour_length(&d, &t) >= two_cost - 1e-9,
"a reversal at ({i}, {j}) still improves"
);
}
}
}
}
}
#[test]
fn christofides_is_within_three_halves_of_optimal() {
let mut rng = Rng::new(0xC471);
for n in 3..=10usize {
for _ in 0..15 {
let d = random_metric(n, &mut rng);
let (cost, tour) = tsp_christofides(&d).expect("the odd set is small here");
assert_eq!(tour.len(), n, "the tour must visit every city");
assert!(crate::discrete::combinatorics::is_permutation(&tour));
assert!(close(cost, tour_length(&d, &tour)), "claimed cost is wrong");
let (opt, _) = traveling_salesman_exact(&d);
assert!(cost <= 1.5 * opt + 1e-9, "n = {n}: {cost} exceeds 1.5 x {opt}");
assert!(cost >= opt - 1e-9);
}
}
}
#[test]
fn chinese_postman_covers_every_edge() {
for g in [cycle_graph(6), complete_graph(5), complete_graph(7)] {
let total: f64 = g.edges().iter().map(|&(_, _, w)| w).sum();
let (cost, walk) = chinese_postman(&g).expect("connected with even degrees");
assert!(close(cost, total), "even-degree cost should be the total");
check_covers(&g, &walk);
}
for g in [path_graph(4), star_graph(5), petersen_graph()] {
let total: f64 = g.edges().iter().map(|&(_, _, w)| w).sum();
let (cost, walk) = chinese_postman(&g).expect("connected");
assert!(cost > total + 1e-9, "odd degrees must force a repeat");
check_covers(&g, &walk);
}
for g in [path_graph(4), path_graph(7), star_graph(6)] {
let total: f64 = g.edges().iter().map(|&(_, _, w)| w).sum();
let (cost, _) = chinese_postman(&g).unwrap();
assert!(close(cost, 2.0 * total), "tree cost {cost} is not 2 x {total}");
}
let split = Graph::from_edges(4, &[(0, 1, 1.0), (2, 3, 1.0)], false);
assert!(chinese_postman(&split).is_none());
assert_eq!(chinese_postman(&Graph::new(4, false)), Some((0.0, vec![0])));
}
fn check_covers(g: &Graph, walk: &[usize]) {
assert_eq!(walk[0], *walk.last().unwrap(), "the route must close");
let mut used: std::collections::BTreeSet<(usize, usize)> =
std::collections::BTreeSet::new();
for w in walk.windows(2) {
assert!(
g.adj[w[0]].iter().any(|&(t, _)| t == w[1]),
"step {w:?} is not an edge"
);
used.insert((w[0].min(w[1]), w[0].max(w[1])));
}
for (u, v, _) in g.edges() {
assert!(
used.contains(&(u.min(v), u.max(v))),
"edge ({u}, {v}) is never crossed"
);
}
}
#[test]
fn min_key_orders_ascending_and_sinks_nan() {
let mut heap = BinaryHeap::new();
for x in [3.0, 1.0, f64::NAN, 2.0, f64::INFINITY] {
heap.push(MinKey(x, 0));
}
let mut popped = Vec::new();
while let Some(MinKey(x, _)) = heap.pop() {
popped.push(x);
}
assert_eq!(popped[0], 1.0);
assert_eq!(popped[1], 2.0);
assert_eq!(popped[2], 3.0);
assert!(popped[3].is_infinite() || popped[3].is_nan());
assert!(popped.iter().any(|x| x.is_nan()));
}
}