ExamplesBy LevelBy TopicLearning Paths
799 Advanced

799-bellman-ford — Bellman-Ford Algorithm

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "799-bellman-ford — Bellman-Ford Algorithm" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. Bellman-Ford (1958/1965) finds shortest paths from a source to all vertices in a weighted graph, handling negative edge weights that Dijkstra cannot handle. Key difference from OCaml: 1. **Overflow guard**: Rust uses `i32::MAX` as infinity and guards with `!= i32::MAX` before adding; OCaml uses `max_int/2` to avoid overflow — both serve the same purpose.

Tutorial

The Problem

Bellman-Ford (1958/1965) finds shortest paths from a source to all vertices in a weighted graph, handling negative edge weights that Dijkstra cannot handle. It also detects negative-weight cycles. Used in network routing (RIP — Routing Information Protocol uses Bellman-Ford), currency arbitrage detection, and financial risk analysis. The O(VE) time complexity is slower than Dijkstra's O((V+E)logV) but handles a broader class of problems.

🎯 Learning Outcomes

  • • Implement Bellman-Ford with V-1 relaxation rounds over all edges
  • • Detect negative-weight cycles by checking if any edge can still be relaxed after V-1 rounds
  • • Understand why V-1 rounds suffice: any shortest path has at most V-1 edges
  • • Return None for graphs with negative cycles (shortest path is undefined)
  • • Compare with Dijkstra: Bellman-Ford is slower but handles negative weights
  • Code Example

    #![allow(clippy::all)]
    //! # Bellman-Ford Algorithm
    
    pub fn bellman_ford(n: usize, edges: &[(usize, usize, i32)], src: usize) -> Option<Vec<i32>> {
        let mut dist = vec![i32::MAX; n];
        dist[src] = 0;
        for _ in 0..n - 1 {
            for &(u, v, w) in edges {
                if dist[u] != i32::MAX && dist[u] + w < dist[v] {
                    dist[v] = dist[u] + w;
                }
            }
        }
        for &(u, v, w) in edges {
            if dist[u] != i32::MAX && dist[u] + w < dist[v] {
                return None; // Negative cycle
            }
        }
        Some(dist)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_bellman() {
            let edges = [
                (0, 1, -1),
                (0, 2, 4),
                (1, 2, 3),
                (1, 3, 2),
                (1, 4, 2),
                (3, 2, 5),
                (3, 1, 1),
                (4, 3, -3),
            ];
            let dist = bellman_ford(5, &edges, 0).unwrap();
            // Shortest paths from 0: 0→1=-1, 0→1→4=1, 0→1→4→3=-2
            assert_eq!(dist[0], 0);
            assert_eq!(dist[1], -1);
            assert_eq!(dist[3], -2);
            assert_eq!(dist[4], 1);
        }
    }

    Key Differences

  • Overflow guard: Rust uses i32::MAX as infinity and guards with != i32::MAX before adding; OCaml uses max_int/2 to avoid overflow — both serve the same purpose.
  • Negative cycle: Both languages return an error/option on negative cycle detection, propagating the undefined result.
  • SPFA optimization: The "Shortest Path Faster Algorithm" uses a queue to skip relaxations that won't improve; this optimization applies equally to both languages.
  • Routing protocols: RIP uses Bellman-Ford with hop count as weight; BGP uses a path-vector variant; both are implemented in production network equipment software.
  • OCaml Approach

    OCaml implements with Array.make n max_int and nested for loops. The negative cycle check adds one more relaxation attempt. Functional style using List.iter over edges is idiomatic. OCaml's Hashtbl can represent the adjacency list. The Int.max_int / 2 sentinel avoids overflow when adding edges to "infinity" values.

    Full Source

    #![allow(clippy::all)]
    //! # Bellman-Ford Algorithm
    
    pub fn bellman_ford(n: usize, edges: &[(usize, usize, i32)], src: usize) -> Option<Vec<i32>> {
        let mut dist = vec![i32::MAX; n];
        dist[src] = 0;
        for _ in 0..n - 1 {
            for &(u, v, w) in edges {
                if dist[u] != i32::MAX && dist[u] + w < dist[v] {
                    dist[v] = dist[u] + w;
                }
            }
        }
        for &(u, v, w) in edges {
            if dist[u] != i32::MAX && dist[u] + w < dist[v] {
                return None; // Negative cycle
            }
        }
        Some(dist)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_bellman() {
            let edges = [
                (0, 1, -1),
                (0, 2, 4),
                (1, 2, 3),
                (1, 3, 2),
                (1, 4, 2),
                (3, 2, 5),
                (3, 1, 1),
                (4, 3, -3),
            ];
            let dist = bellman_ford(5, &edges, 0).unwrap();
            // Shortest paths from 0: 0→1=-1, 0→1→4=1, 0→1→4→3=-2
            assert_eq!(dist[0], 0);
            assert_eq!(dist[1], -1);
            assert_eq!(dist[3], -2);
            assert_eq!(dist[4], 1);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_bellman() {
            let edges = [
                (0, 1, -1),
                (0, 2, 4),
                (1, 2, 3),
                (1, 3, 2),
                (1, 4, 2),
                (3, 2, 5),
                (3, 1, 1),
                (4, 3, -3),
            ];
            let dist = bellman_ford(5, &edges, 0).unwrap();
            // Shortest paths from 0: 0→1=-1, 0→1→4=1, 0→1→4→3=-2
            assert_eq!(dist[0], 0);
            assert_eq!(dist[1], -1);
            assert_eq!(dist[3], -2);
            assert_eq!(dist[4], 1);
        }
    }

    Deep Comparison

    OCaml vs Rust: Bellman Ford

    Overview

    See the example.rs and example.ml files for detailed implementations.

    Key Differences

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • Implement SPFA (Bellman-Ford with a queue): only relax edges from nodes whose distance was recently updated. Benchmark against naive Bellman-Ford on sparse graphs.
  • Add bellman_ford_path(n, edges, src, dst) -> Option<Vec<usize>> that reconstructs the shortest path from src to dst using a predecessor array.
  • Use Bellman-Ford to detect currency arbitrage: given exchange rates as a complete graph with log-weight edges, a negative cycle indicates an arbitrage opportunity.
  • Open Source Repos