Skip to main content

oxilean_std/graph_algorithms/
functions.rs

1//! Advanced graph algorithm implementations.
2
3use std::cmp::Reverse;
4use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
5
6use super::types::{
7    AllPairsShortestPath, FlowNetwork, MatchingResult, SpanningTree, StronglyConnectedComponents,
8    WGraph,
9};
10
11// ── Union-Find (Disjoint Set Union) ──────────────────────────────────────────
12
13struct Dsu {
14    parent: Vec<usize>,
15    rank: Vec<usize>,
16}
17
18impl Dsu {
19    fn new(n: usize) -> Self {
20        Self {
21            parent: (0..n).collect(),
22            rank: vec![0; n],
23        }
24    }
25
26    fn find(&mut self, x: usize) -> usize {
27        if self.parent[x] != x {
28            self.parent[x] = self.find(self.parent[x]);
29        }
30        self.parent[x]
31    }
32
33    fn union(&mut self, a: usize, b: usize) -> bool {
34        let ra = self.find(a);
35        let rb = self.find(b);
36        if ra == rb {
37            return false;
38        }
39        match self.rank[ra].cmp(&self.rank[rb]) {
40            std::cmp::Ordering::Less => self.parent[ra] = rb,
41            std::cmp::Ordering::Greater => self.parent[rb] = ra,
42            std::cmp::Ordering::Equal => {
43                self.parent[rb] = ra;
44                self.rank[ra] += 1;
45            }
46        }
47        true
48    }
49}
50
51// ── Shortest Paths ────────────────────────────────────────────────────────────
52
53/// Dijkstra's algorithm for single-source shortest paths (non-negative weights).
54///
55/// Returns `dist\[v\]` = `Some(d)` if reachable, `None` otherwise.
56/// Panics (via bounds check) if `start >= g.n`.
57pub fn dijkstra(g: &WGraph, start: usize) -> Vec<Option<i64>> {
58    let n = g.n;
59    let inf = i64::MAX / 2;
60    let mut dist = vec![inf; n];
61    dist[start] = 0;
62    let mut heap: BinaryHeap<Reverse<(i64, usize)>> = BinaryHeap::new();
63    heap.push(Reverse((0, start)));
64
65    while let Some(Reverse((d, u))) = heap.pop() {
66        if d > dist[u] {
67            continue;
68        }
69        for &(v, w) in &g.adj[u] {
70            let nd = dist[u].saturating_add(w);
71            if nd < dist[v] {
72                dist[v] = nd;
73                heap.push(Reverse((nd, v)));
74            }
75        }
76    }
77
78    dist.into_iter()
79        .map(|d| if d == inf { None } else { Some(d) })
80        .collect()
81}
82
83/// Bellman-Ford algorithm for single-source shortest paths (allows negative weights).
84///
85/// Returns `Ok(dist)` with `None` for unreachable vertices, or `Err(msg)` if a
86/// negative-weight cycle reachable from `start` is detected.
87pub fn bellman_ford(g: &WGraph, start: usize) -> Result<Vec<Option<i64>>, String> {
88    let n = g.n;
89    let inf = i64::MAX / 2;
90    let mut dist = vec![inf; n];
91    dist[start] = 0;
92
93    // n-1 relaxation rounds
94    for _ in 0..n.saturating_sub(1) {
95        let mut changed = false;
96        for u in 0..n {
97            if dist[u] == inf {
98                continue;
99            }
100            for &(v, w) in &g.adj[u] {
101                let nd = dist[u].saturating_add(w);
102                if nd < dist[v] {
103                    dist[v] = nd;
104                    changed = true;
105                }
106            }
107        }
108        if !changed {
109            break;
110        }
111    }
112
113    // Negative-cycle detection: one more round
114    for u in 0..n {
115        if dist[u] == inf {
116            continue;
117        }
118        for &(v, w) in &g.adj[u] {
119            let nd = dist[u].saturating_add(w);
120            if nd < dist[v] {
121                return Err(format!(
122                    "Negative-weight cycle detected reachable from vertex {}",
123                    start
124                ));
125            }
126        }
127    }
128
129    Ok(dist
130        .into_iter()
131        .map(|d| if d == inf { None } else { Some(d) })
132        .collect())
133}
134
135/// Floyd-Warshall all-pairs shortest paths with path reconstruction.
136///
137/// Handles negative weights but not negative cycles.
138/// For vertices on a negative cycle, distances are set to `None`.
139pub fn floyd_warshall(g: &WGraph) -> AllPairsShortestPath {
140    let n = g.n;
141    let inf = i64::MAX / 4;
142
143    let mut d = vec![vec![inf; n]; n];
144    let mut next: Vec<Vec<Option<usize>>> = vec![vec![None; n]; n];
145
146    for u in 0..n {
147        d[u][u] = 0;
148        for &(v, w) in &g.adj[u] {
149            if w < d[u][v] {
150                d[u][v] = w;
151                next[u][v] = Some(v);
152            }
153        }
154    }
155
156    for k in 0..n {
157        for i in 0..n {
158            for j in 0..n {
159                if d[i][k] < inf && d[k][j] < inf {
160                    let through = d[i][k] + d[k][j];
161                    if through < d[i][j] {
162                        d[i][j] = through;
163                        next[i][j] = next[i][k];
164                    }
165                }
166            }
167        }
168    }
169
170    // Convert to Option, mapping inf to None
171    let dist = d
172        .into_iter()
173        .map(|row| {
174            row.into_iter()
175                .map(|v| if v >= inf { None } else { Some(v) })
176                .collect()
177        })
178        .collect();
179
180    AllPairsShortestPath { dist, next }
181}
182
183// ── Minimum Spanning Tree ─────────────────────────────────────────────────────
184
185/// Kruskal's MST algorithm on an undirected weighted graph given as an edge list.
186///
187/// `edges` = slice of `(u, v, weight)`. Works on undirected edges.
188/// Returns the spanning tree (or forest if disconnected).
189pub fn kruskal_mst(n: usize, edges: &[(usize, usize, i64)]) -> SpanningTree {
190    let mut sorted: Vec<(i64, usize, usize)> = edges.iter().map(|&(u, v, w)| (w, u, v)).collect();
191    sorted.sort_unstable();
192
193    let mut dsu = Dsu::new(n);
194    let mut tree_edges: Vec<(usize, usize, i64)> = Vec::new();
195    let mut total_weight = 0i64;
196
197    for (w, u, v) in sorted {
198        if dsu.union(u, v) {
199            tree_edges.push((u, v, w));
200            total_weight += w;
201        }
202    }
203
204    SpanningTree {
205        edges: tree_edges,
206        total_weight,
207    }
208}
209
210/// Prim's MST algorithm on a weighted directed graph.
211///
212/// Treats the graph as undirected (uses both u→v and v→u edges if present).
213/// Starts from vertex 0.
214pub fn prim_mst(g: &WGraph) -> SpanningTree {
215    let n = g.n;
216    if n == 0 {
217        return SpanningTree {
218            edges: vec![],
219            total_weight: 0,
220        };
221    }
222
223    // Build undirected adjacency for Prim
224    let mut adj: Vec<Vec<(usize, i64)>> = vec![vec![]; n];
225    for u in 0..n {
226        for &(v, w) in &g.adj[u] {
227            adj[u].push((v, w));
228            adj[v].push((u, w));
229        }
230    }
231
232    let inf = i64::MAX / 2;
233    let mut in_tree = vec![false; n];
234    let mut min_edge: Vec<i64> = vec![inf; n];
235    let mut parent: Vec<Option<usize>> = vec![None; n];
236    min_edge[0] = 0;
237
238    // Min-heap: (cost, vertex, from_vertex)
239    let mut heap: BinaryHeap<Reverse<(i64, usize, usize)>> = BinaryHeap::new();
240    heap.push(Reverse((0, 0, 0)));
241
242    let mut tree_edges: Vec<(usize, usize, i64)> = Vec::new();
243    let mut total_weight = 0i64;
244
245    while let Some(Reverse((cost, u, from))) = heap.pop() {
246        if in_tree[u] {
247            continue;
248        }
249        in_tree[u] = true;
250        if u != 0 {
251            tree_edges.push((from, u, cost));
252            total_weight += cost;
253        }
254
255        for &(v, w) in &adj[u] {
256            if !in_tree[v] && w < min_edge[v] {
257                min_edge[v] = w;
258                parent[v] = Some(u);
259                heap.push(Reverse((w, v, u)));
260            }
261        }
262    }
263
264    SpanningTree {
265        edges: tree_edges,
266        total_weight,
267    }
268}
269
270// ── Max-Flow / Min-Cut ────────────────────────────────────────────────────────
271
272/// Edmonds-Karp max-flow (BFS-based Ford-Fulkerson on `FlowNetwork`).
273///
274/// Modifies the flow network in-place and returns the total max-flow value.
275pub fn max_flow_bfs(net: &mut FlowNetwork) -> i64 {
276    let source = net.source;
277    let sink = net.sink;
278    let mut total_flow = 0i64;
279
280    loop {
281        // BFS to find augmenting path
282        let mut prev_edge: Vec<Option<usize>> = vec![None; net.n];
283        let mut visited = vec![false; net.n];
284        let mut queue = VecDeque::new();
285        visited[source] = true;
286        queue.push_back(source);
287
288        'bfs: while let Some(u) = queue.pop_front() {
289            for &eid in &net.graph[u].clone() {
290                let v = net.edges[eid].to;
291                if !visited[v] && net.residual(eid) > 0 {
292                    visited[v] = true;
293                    prev_edge[v] = Some(eid);
294                    if v == sink {
295                        break 'bfs;
296                    }
297                    queue.push_back(v);
298                }
299            }
300        }
301
302        if !visited[sink] {
303            break;
304        }
305
306        // Find bottleneck
307        let mut bottleneck = i64::MAX;
308        let mut cur = sink;
309        while cur != source {
310            if let Some(eid) = prev_edge[cur] {
311                bottleneck = bottleneck.min(net.residual(eid));
312                cur = net.edges[eid].from;
313            } else {
314                break;
315            }
316        }
317
318        // Update flows
319        cur = sink;
320        while cur != source {
321            if let Some(eid) = prev_edge[cur] {
322                net.edges[eid].flow += bottleneck;
323                let rev = net.edges[eid].rev;
324                net.edges[rev].flow -= bottleneck;
325                cur = net.edges[eid].from;
326            } else {
327                break;
328            }
329        }
330
331        total_flow += bottleneck;
332    }
333
334    total_flow
335}
336
337/// Compute the S/T partition (min-cut) after running `max_flow_bfs`.
338///
339/// Returns `(S, T)` where `S` contains vertices reachable from source in the residual graph
340/// and `T` contains the rest.
341pub fn min_cut(net: &FlowNetwork, _max_flow_val: i64) -> (Vec<usize>, Vec<usize>) {
342    let source = net.source;
343    let n = net.n;
344    let mut reachable = vec![false; n];
345    let mut queue = VecDeque::new();
346    reachable[source] = true;
347    queue.push_back(source);
348
349    while let Some(u) = queue.pop_front() {
350        for &eid in &net.graph[u] {
351            let v = net.edges[eid].to;
352            if !reachable[v] && net.residual(eid) > 0 {
353                reachable[v] = true;
354                queue.push_back(v);
355            }
356        }
357    }
358
359    let s_side: Vec<usize> = (0..n).filter(|&v| reachable[v]).collect();
360    let t_side: Vec<usize> = (0..n).filter(|&v| !reachable[v]).collect();
361    (s_side, t_side)
362}
363
364// ── Bipartite Matching (Hopcroft-Karp) ───────────────────────────────────────
365
366/// Hopcroft-Karp bipartite matching.
367///
368/// `left` = number of left vertices (0..left), `right` = number of right vertices (0..right).
369/// `edges` = list of `(left_vertex, right_vertex)` pairs.
370/// Returns a `MatchingResult` with `matching\[l\]` = `Some(r)` for matched pairs.
371pub fn bipartite_matching(left: usize, right: usize, edges: &[(usize, usize)]) -> MatchingResult {
372    // Build adjacency list for left vertices
373    let mut adj: Vec<Vec<usize>> = vec![vec![]; left];
374    for &(l, r) in edges {
375        adj[l].push(r);
376    }
377
378    let inf = usize::MAX;
379    let mut match_l: Vec<Option<usize>> = vec![None; left];
380    let mut match_r: Vec<Option<usize>> = vec![None; right];
381
382    loop {
383        // BFS phase: build layered graph
384        let mut dist: Vec<usize> = vec![inf; left];
385        let mut queue = VecDeque::new();
386
387        for l in 0..left {
388            if match_l[l].is_none() {
389                dist[l] = 0;
390                queue.push_back(l);
391            }
392        }
393
394        let mut found = false;
395        while let Some(l) = queue.pop_front() {
396            for &r in &adj[l] {
397                let nl = match_r[r];
398                match nl {
399                    None => {
400                        found = true;
401                    }
402                    Some(nl_idx) => {
403                        if dist[nl_idx] == inf {
404                            dist[nl_idx] = dist[l] + 1;
405                            queue.push_back(nl_idx);
406                        }
407                    }
408                }
409            }
410        }
411
412        if !found {
413            break;
414        }
415
416        // DFS phase
417        for l in 0..left {
418            if match_l[l].is_none() {
419                dfs_hopcroft(l, &adj, &mut match_l, &mut match_r, &mut dist, inf);
420            }
421        }
422    }
423
424    let size = match_l.iter().filter(|m| m.is_some()).count();
425    MatchingResult {
426        matching: match_l,
427        size,
428    }
429}
430
431fn dfs_hopcroft(
432    l: usize,
433    adj: &[Vec<usize>],
434    match_l: &mut Vec<Option<usize>>,
435    match_r: &mut Vec<Option<usize>>,
436    dist: &mut Vec<usize>,
437    inf: usize,
438) -> bool {
439    for &r in &adj[l] {
440        let ok = match match_r[r] {
441            None => true,
442            Some(nl) => {
443                dist[nl] == dist[l] + 1 && dfs_hopcroft(nl, adj, match_l, match_r, dist, inf)
444            }
445        };
446        if ok {
447            match_l[l] = Some(r);
448            match_r[r] = Some(l);
449            return true;
450        }
451    }
452    dist[l] = inf;
453    false
454}
455
456// ── Tarjan's SCC ──────────────────────────────────────────────────────────────
457
458/// Tarjan's strongly connected components algorithm.
459///
460/// Returns SCCs in reverse topological order (each SCC before its successors in condensation).
461pub fn tarjan_scc(g: &WGraph) -> StronglyConnectedComponents {
462    let n = g.n;
463    let mut index_counter = 0usize;
464    let mut stack: Vec<usize> = Vec::new();
465    let mut on_stack = vec![false; n];
466    let mut index: Vec<Option<usize>> = vec![None; n];
467    let mut lowlink = vec![0usize; n];
468    let mut components: Vec<Vec<usize>> = Vec::new();
469    let mut component_of: Vec<usize> = vec![0; n];
470
471    for v in 0..n {
472        if index[v].is_none() {
473            tarjan_visit(
474                v,
475                g,
476                &mut index_counter,
477                &mut stack,
478                &mut on_stack,
479                &mut index,
480                &mut lowlink,
481                &mut components,
482                &mut component_of,
483            );
484        }
485    }
486
487    StronglyConnectedComponents {
488        components,
489        component_of,
490    }
491}
492
493#[allow(clippy::too_many_arguments)]
494fn tarjan_visit(
495    v: usize,
496    g: &WGraph,
497    index_counter: &mut usize,
498    stack: &mut Vec<usize>,
499    on_stack: &mut Vec<bool>,
500    index: &mut Vec<Option<usize>>,
501    lowlink: &mut Vec<usize>,
502    components: &mut Vec<Vec<usize>>,
503    component_of: &mut Vec<usize>,
504) {
505    // Iterative Tarjan using explicit call stack to avoid stack overflow on large graphs.
506    // Each frame: (vertex, edge_iterator_position, index_at_entry)
507    let mut call_stack: Vec<(usize, usize)> = vec![(v, 0)];
508    index[v] = Some(*index_counter);
509    lowlink[v] = *index_counter;
510    *index_counter += 1;
511    stack.push(v);
512    on_stack[v] = true;
513
514    while let Some((u, ei)) = call_stack.last_mut() {
515        let u = *u;
516        let neighbors = &g.adj[u];
517        if *ei < neighbors.len() {
518            let (w, _) = neighbors[*ei];
519            *ei += 1;
520            if index[w].is_none() {
521                // Recurse into w
522                index[w] = Some(*index_counter);
523                lowlink[w] = *index_counter;
524                *index_counter += 1;
525                stack.push(w);
526                on_stack[w] = true;
527                call_stack.push((w, 0));
528            } else if on_stack[w] {
529                let lw = lowlink[w];
530                let lu = lowlink[u];
531                lowlink[u] = lu.min(lw);
532                // Update the frame's lowlink by modifying through the stack
533                if let Some(frame) = call_stack.last_mut() {
534                    let _ = frame; // lowlink[u] already updated
535                }
536            }
537        } else {
538            // Done with u; pop
539            call_stack.pop();
540
541            // Propagate lowlink upward
542            if let Some(&(parent, _)) = call_stack.last() {
543                lowlink[parent] = lowlink[parent].min(lowlink[u]);
544            }
545
546            // Check if u is root of an SCC
547            if let Some(idx_u) = index[u] {
548                if lowlink[u] == idx_u {
549                    let comp_id = components.len();
550                    let mut comp = Vec::new();
551                    loop {
552                        let w = stack.pop().unwrap_or(u);
553                        on_stack[w] = false;
554                        comp.push(w);
555                        if w == u {
556                            break;
557                        }
558                    }
559                    for &node in &comp {
560                        component_of[node] = comp_id;
561                    }
562                    components.push(comp);
563                }
564            }
565        }
566    }
567}
568
569// ── Topological Sort ──────────────────────────────────────────────────────────
570
571/// Topological sort of a DAG using Kahn's algorithm (BFS).
572///
573/// Returns `Some(order)` if the graph is acyclic, `None` if a cycle exists.
574pub fn topological_sort_dag(g: &WGraph) -> Option<Vec<usize>> {
575    let n = g.n;
576    let mut in_degree = vec![0usize; n];
577    for u in 0..n {
578        for &(v, _) in &g.adj[u] {
579            in_degree[v] += 1;
580        }
581    }
582
583    let mut queue: VecDeque<usize> = (0..n).filter(|&u| in_degree[u] == 0).collect();
584    let mut order = Vec::with_capacity(n);
585
586    while let Some(u) = queue.pop_front() {
587        order.push(u);
588        for &(v, _) in &g.adj[u] {
589            in_degree[v] -= 1;
590            if in_degree[v] == 0 {
591                queue.push_back(v);
592            }
593        }
594    }
595
596    if order.len() == n {
597        Some(order)
598    } else {
599        None
600    }
601}
602
603// ── Bipartiteness ─────────────────────────────────────────────────────────────
604
605/// Check if the graph is bipartite using BFS 2-coloring.
606///
607/// Treats edges as undirected. Returns `Some((left, right))` if bipartite, `None` otherwise.
608pub fn is_bipartite(g: &WGraph) -> Option<(Vec<usize>, Vec<usize>)> {
609    let n = g.n;
610    let no_color = usize::MAX;
611    let mut color = vec![no_color; n];
612
613    for start in 0..n {
614        if color[start] != no_color {
615            continue;
616        }
617        color[start] = 0;
618        let mut queue = VecDeque::new();
619        queue.push_back(start);
620
621        while let Some(u) = queue.pop_front() {
622            for &(v, _) in &g.adj[u] {
623                if color[v] == no_color {
624                    color[v] = 1 - color[u];
625                    queue.push_back(v);
626                } else if color[v] == color[u] {
627                    return None;
628                }
629            }
630        }
631    }
632
633    let left: Vec<usize> = (0..n).filter(|&v| color[v] == 0).collect();
634    let right: Vec<usize> = (0..n).filter(|&v| color[v] == 1).collect();
635    Some((left, right))
636}
637
638// ── Chromatic Number Approximation ────────────────────────────────────────────
639
640/// Greedy approximation of the chromatic number (treats graph as undirected).
641///
642/// Uses a greedy coloring heuristic; may overestimate the true chromatic number.
643pub fn chromatic_number_approx(g: &WGraph) -> usize {
644    let n = g.n;
645    if n == 0 {
646        return 0;
647    }
648
649    // Build undirected adjacency for coloring
650    let mut undirected: Vec<HashSet<usize>> = vec![HashSet::new(); n];
651    for u in 0..n {
652        for &(v, _) in &g.adj[u] {
653            undirected[u].insert(v);
654            undirected[v].insert(u);
655        }
656    }
657
658    let mut color = vec![usize::MAX; n];
659    let mut max_color = 0usize;
660
661    for u in 0..n {
662        let neighbor_colors: HashSet<usize> = undirected[u]
663            .iter()
664            .filter_map(|&v| {
665                if color[v] != usize::MAX {
666                    Some(color[v])
667                } else {
668                    None
669                }
670            })
671            .collect();
672
673        let mut c = 0usize;
674        while neighbor_colors.contains(&c) {
675            c += 1;
676        }
677        color[u] = c;
678        if c > max_color {
679            max_color = c;
680        }
681    }
682
683    max_color + 1
684}
685
686// ── Tests ─────────────────────────────────────────────────────────────────────
687
688#[cfg(test)]
689mod tests {
690    use super::super::types::{FlowNetwork, WGraph};
691    use super::*;
692
693    fn simple_dag() -> WGraph {
694        // 0→1(1), 0→2(4), 1→2(2), 1→3(5), 2→3(1)
695        let mut g = WGraph::new(4);
696        g.add_edge(0, 1, 1);
697        g.add_edge(0, 2, 4);
698        g.add_edge(1, 2, 2);
699        g.add_edge(1, 3, 5);
700        g.add_edge(2, 3, 1);
701        g
702    }
703
704    // ── Dijkstra ───────────────────────────────────────────────────────────────
705
706    #[test]
707    fn test_dijkstra_basic() {
708        let g = simple_dag();
709        let dist = dijkstra(&g, 0);
710        assert_eq!(dist[0], Some(0));
711        assert_eq!(dist[1], Some(1));
712        assert_eq!(dist[2], Some(3)); // 0→1→2
713        assert_eq!(dist[3], Some(4)); // 0→1→2→3
714    }
715
716    #[test]
717    fn test_dijkstra_unreachable() {
718        let mut g = WGraph::new(3);
719        g.add_edge(0, 1, 5);
720        // vertex 2 is unreachable
721        let dist = dijkstra(&g, 0);
722        assert_eq!(dist[2], None);
723    }
724
725    #[test]
726    fn test_dijkstra_single_vertex() {
727        let g = WGraph::new(1);
728        let dist = dijkstra(&g, 0);
729        assert_eq!(dist[0], Some(0));
730    }
731
732    #[test]
733    fn test_dijkstra_self_loop() {
734        let mut g = WGraph::new(2);
735        g.add_edge(0, 0, 5);
736        g.add_edge(0, 1, 3);
737        let dist = dijkstra(&g, 0);
738        assert_eq!(dist[1], Some(3));
739    }
740
741    // ── Bellman-Ford ───────────────────────────────────────────────────────────
742
743    #[test]
744    fn test_bellman_ford_positive_weights() {
745        let g = simple_dag();
746        let dist = bellman_ford(&g, 0).expect("no negative cycle");
747        assert_eq!(dist[3], Some(4));
748    }
749
750    #[test]
751    fn test_bellman_ford_negative_weights() {
752        let mut g = WGraph::new(3);
753        g.add_edge(0, 1, -2);
754        g.add_edge(1, 2, 3);
755        g.add_edge(0, 2, 4);
756        let dist = bellman_ford(&g, 0).expect("no negative cycle");
757        assert_eq!(dist[2], Some(1)); // 0→1→2 = -2+3 = 1
758    }
759
760    #[test]
761    fn test_bellman_ford_negative_cycle() {
762        let mut g = WGraph::new(3);
763        g.add_edge(0, 1, 1);
764        g.add_edge(1, 2, -3);
765        g.add_edge(2, 0, 1);
766        let result = bellman_ford(&g, 0);
767        assert!(result.is_err());
768    }
769
770    #[test]
771    fn test_bellman_ford_unreachable() {
772        let mut g = WGraph::new(3);
773        g.add_edge(0, 1, 1);
774        let dist = bellman_ford(&g, 0).expect("no negative cycle");
775        assert_eq!(dist[2], None);
776    }
777
778    // ── Floyd-Warshall ─────────────────────────────────────────────────────────
779
780    #[test]
781    fn test_floyd_warshall_basic() {
782        let g = simple_dag();
783        let apsp = floyd_warshall(&g);
784        assert_eq!(apsp.dist[0][3], Some(4));
785        assert_eq!(apsp.dist[1][3], Some(3));
786    }
787
788    #[test]
789    fn test_floyd_warshall_no_path() {
790        let mut g = WGraph::new(3);
791        g.add_edge(0, 1, 2);
792        let apsp = floyd_warshall(&g);
793        assert_eq!(apsp.dist[0][2], None);
794        assert_eq!(apsp.dist[1][2], None);
795    }
796
797    #[test]
798    fn test_floyd_warshall_next_hops() {
799        let mut g = WGraph::new(3);
800        g.add_edge(0, 1, 1);
801        g.add_edge(1, 2, 1);
802        let apsp = floyd_warshall(&g);
803        assert_eq!(apsp.next[0][2], Some(1)); // 0→1→2
804    }
805
806    // ── Kruskal ────────────────────────────────────────────────────────────────
807
808    #[test]
809    fn test_kruskal_basic() {
810        let edges = vec![(0, 1, 1), (0, 2, 3), (1, 2, 2), (1, 3, 4), (2, 3, 5)];
811        let mst = kruskal_mst(4, &edges);
812        assert_eq!(mst.edges.len(), 3);
813        assert_eq!(mst.total_weight, 7); // 1+2+4
814    }
815
816    #[test]
817    fn test_kruskal_empty() {
818        let mst = kruskal_mst(3, &[]);
819        assert_eq!(mst.edges.len(), 0);
820        assert_eq!(mst.total_weight, 0);
821    }
822
823    #[test]
824    fn test_kruskal_single_edge() {
825        let edges = vec![(0, 1, 5)];
826        let mst = kruskal_mst(2, &edges);
827        assert_eq!(mst.total_weight, 5);
828    }
829
830    // ── Prim ──────────────────────────────────────────────────────────────────
831
832    #[test]
833    fn test_prim_basic() {
834        let mut g = WGraph::new(4);
835        g.add_edge(0, 1, 1);
836        g.add_edge(1, 0, 1);
837        g.add_edge(0, 2, 3);
838        g.add_edge(2, 0, 3);
839        g.add_edge(1, 2, 2);
840        g.add_edge(2, 1, 2);
841        g.add_edge(1, 3, 4);
842        g.add_edge(3, 1, 4);
843        let mst = prim_mst(&g);
844        assert_eq!(mst.edges.len(), 3);
845        assert_eq!(mst.total_weight, 7);
846    }
847
848    #[test]
849    fn test_prim_empty() {
850        let g = WGraph::new(0);
851        let mst = prim_mst(&g);
852        assert_eq!(mst.edges.len(), 0);
853    }
854
855    // ── Max-Flow ──────────────────────────────────────────────────────────────
856
857    #[test]
858    fn test_max_flow_basic() {
859        let mut net = FlowNetwork::new(4, 0, 3);
860        net.add_edge(0, 1, 3);
861        net.add_edge(0, 2, 2);
862        net.add_edge(1, 3, 2);
863        net.add_edge(2, 3, 3);
864        let flow = max_flow_bfs(&mut net);
865        assert_eq!(flow, 4);
866    }
867
868    #[test]
869    fn test_max_flow_no_path() {
870        let mut net = FlowNetwork::new(4, 0, 3);
871        net.add_edge(0, 1, 10);
872        net.add_edge(2, 3, 10);
873        // no path from source to sink
874        let flow = max_flow_bfs(&mut net);
875        assert_eq!(flow, 0);
876    }
877
878    #[test]
879    fn test_max_flow_single_edge() {
880        let mut net = FlowNetwork::new(2, 0, 1);
881        net.add_edge(0, 1, 7);
882        let flow = max_flow_bfs(&mut net);
883        assert_eq!(flow, 7);
884    }
885
886    // ── Min-Cut ───────────────────────────────────────────────────────────────
887
888    #[test]
889    fn test_min_cut_basic() {
890        let mut net = FlowNetwork::new(4, 0, 3);
891        net.add_edge(0, 1, 3);
892        net.add_edge(0, 2, 2);
893        net.add_edge(1, 3, 2);
894        net.add_edge(2, 3, 3);
895        let flow_val = max_flow_bfs(&mut net);
896        let (s_side, t_side) = min_cut(&net, flow_val);
897        assert!(s_side.contains(&0));
898        assert!(t_side.contains(&3));
899    }
900
901    // ── Bipartite Matching ────────────────────────────────────────────────────
902
903    #[test]
904    fn test_bipartite_matching_perfect() {
905        // 3x3 complete bipartite → perfect matching of size 3
906        let edges: Vec<(usize, usize)> = (0..3).flat_map(|l| (0..3).map(move |r| (l, r))).collect();
907        let result = bipartite_matching(3, 3, &edges);
908        assert_eq!(result.size, 3);
909    }
910
911    #[test]
912    fn test_bipartite_matching_partial() {
913        // Only edge 0→0
914        let edges = vec![(0, 0)];
915        let result = bipartite_matching(2, 2, &edges);
916        assert_eq!(result.size, 1);
917        assert_eq!(result.matching[0], Some(0));
918    }
919
920    #[test]
921    fn test_bipartite_matching_empty() {
922        let result = bipartite_matching(3, 3, &[]);
923        assert_eq!(result.size, 0);
924    }
925
926    #[test]
927    fn test_bipartite_matching_chain() {
928        // 0→0, 1→1, 2→2 (no sharing)
929        let edges = vec![(0, 0), (1, 1), (2, 2)];
930        let result = bipartite_matching(3, 3, &edges);
931        assert_eq!(result.size, 3);
932    }
933
934    // ── Tarjan SCC ────────────────────────────────────────────────────────────
935
936    #[test]
937    fn test_tarjan_scc_single_cycle() {
938        let mut g = WGraph::new(3);
939        g.add_edge(0, 1, 1);
940        g.add_edge(1, 2, 1);
941        g.add_edge(2, 0, 1);
942        let scc = tarjan_scc(&g);
943        assert_eq!(scc.components.len(), 1);
944        assert_eq!(scc.components[0].len(), 3);
945    }
946
947    #[test]
948    fn test_tarjan_scc_dag() {
949        let g = simple_dag();
950        let scc = tarjan_scc(&g);
951        // All trivial SCCs (no back edges)
952        assert_eq!(scc.components.len(), 4);
953    }
954
955    #[test]
956    fn test_tarjan_scc_two_components() {
957        let mut g = WGraph::new(4);
958        g.add_edge(0, 1, 1);
959        g.add_edge(1, 0, 1);
960        g.add_edge(2, 3, 1);
961        g.add_edge(3, 2, 1);
962        let scc = tarjan_scc(&g);
963        assert_eq!(scc.components.len(), 2);
964    }
965
966    // ── Topological Sort ──────────────────────────────────────────────────────
967
968    #[test]
969    fn test_topological_sort_dag() {
970        let g = simple_dag();
971        let order = topological_sort_dag(&g).expect("DAG");
972        // Check order is valid: for each edge u→v, u appears before v
973        let pos: Vec<usize> = {
974            let mut p = vec![0usize; g.n];
975            for (i, &v) in order.iter().enumerate() {
976                p[v] = i;
977            }
978            p
979        };
980        for u in 0..g.n {
981            for &(v, _) in &g.adj[u] {
982                assert!(pos[u] < pos[v], "edge {} → {} violates order", u, v);
983            }
984        }
985    }
986
987    #[test]
988    fn test_topological_sort_cyclic() {
989        let mut g = WGraph::new(3);
990        g.add_edge(0, 1, 1);
991        g.add_edge(1, 2, 1);
992        g.add_edge(2, 0, 1);
993        assert!(topological_sort_dag(&g).is_none());
994    }
995
996    // ── Is Bipartite ──────────────────────────────────────────────────────────
997
998    #[test]
999    fn test_is_bipartite_yes() {
1000        // Even cycle: 0→1→2→3→0 (treat as undirected)
1001        let mut g = WGraph::new(4);
1002        g.add_edge(0, 1, 1);
1003        g.add_edge(1, 0, 1);
1004        g.add_edge(1, 2, 1);
1005        g.add_edge(2, 1, 1);
1006        g.add_edge(2, 3, 1);
1007        g.add_edge(3, 2, 1);
1008        g.add_edge(3, 0, 1);
1009        g.add_edge(0, 3, 1);
1010        let result = is_bipartite(&g);
1011        assert!(result.is_some());
1012    }
1013
1014    #[test]
1015    fn test_is_bipartite_no_odd_cycle() {
1016        // Triangle (odd cycle)
1017        let mut g = WGraph::new(3);
1018        g.add_edge(0, 1, 1);
1019        g.add_edge(1, 0, 1);
1020        g.add_edge(1, 2, 1);
1021        g.add_edge(2, 1, 1);
1022        g.add_edge(2, 0, 1);
1023        g.add_edge(0, 2, 1);
1024        assert!(is_bipartite(&g).is_none());
1025    }
1026
1027    #[test]
1028    fn test_is_bipartite_empty() {
1029        let g = WGraph::new(3);
1030        let result = is_bipartite(&g);
1031        assert!(result.is_some());
1032    }
1033
1034    // ── Chromatic Number Approx ───────────────────────────────────────────────
1035
1036    #[test]
1037    fn test_chromatic_number_path() {
1038        // Path: 0→1→2 undirected → bipartite → 2 colors
1039        let mut g = WGraph::new(3);
1040        g.add_edge(0, 1, 1);
1041        g.add_edge(1, 0, 1);
1042        g.add_edge(1, 2, 1);
1043        g.add_edge(2, 1, 1);
1044        let chi = chromatic_number_approx(&g);
1045        assert_eq!(chi, 2);
1046    }
1047
1048    #[test]
1049    fn test_chromatic_number_triangle() {
1050        // Triangle needs 3 colors
1051        let mut g = WGraph::new(3);
1052        g.add_edge(0, 1, 1);
1053        g.add_edge(1, 0, 1);
1054        g.add_edge(1, 2, 1);
1055        g.add_edge(2, 1, 1);
1056        g.add_edge(0, 2, 1);
1057        g.add_edge(2, 0, 1);
1058        let chi = chromatic_number_approx(&g);
1059        assert_eq!(chi, 3);
1060    }
1061
1062    #[test]
1063    fn test_chromatic_number_empty() {
1064        let g = WGraph::new(0);
1065        assert_eq!(chromatic_number_approx(&g), 0);
1066    }
1067
1068    #[test]
1069    fn test_chromatic_number_single() {
1070        let g = WGraph::new(1);
1071        assert_eq!(chromatic_number_approx(&g), 1);
1072    }
1073
1074    #[test]
1075    fn test_chromatic_number_independent_set() {
1076        // 4 isolated vertices → 1 color
1077        let g = WGraph::new(4);
1078        assert_eq!(chromatic_number_approx(&g), 1);
1079    }
1080
1081    // ── Combined / Edge cases ─────────────────────────────────────────────────
1082
1083    #[test]
1084    fn test_dijkstra_zero_weight() {
1085        let mut g = WGraph::new(3);
1086        g.add_edge(0, 1, 0);
1087        g.add_edge(1, 2, 0);
1088        let dist = dijkstra(&g, 0);
1089        assert_eq!(dist[2], Some(0));
1090    }
1091
1092    #[test]
1093    fn test_kruskal_negative_weights() {
1094        let edges = vec![(0, 1, -5), (1, 2, -3), (0, 2, -1)];
1095        let mst = kruskal_mst(3, &edges);
1096        assert_eq!(mst.total_weight, -8); // -5 + -3
1097    }
1098
1099    #[test]
1100    fn test_floyd_warshall_self_loop() {
1101        let mut g = WGraph::new(2);
1102        g.add_edge(0, 0, 100);
1103        g.add_edge(0, 1, 5);
1104        let apsp = floyd_warshall(&g);
1105        assert_eq!(apsp.dist[0][0], Some(0));
1106        assert_eq!(apsp.dist[0][1], Some(5));
1107    }
1108}