ExamplesBy LevelBy TopicLearning Paths
807 Advanced

807-bipartite-check — Bipartite Check

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "807-bipartite-check — Bipartite Check" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. A bipartite graph can be 2-colored: vertices split into two sets where all edges go between sets (no edges within a set). Key difference from OCaml: 1. **BFS queue**: Rust uses `VecDeque<usize>` (FIFO); OCaml uses `Queue.t` — equivalent data structures.

Tutorial

The Problem

A bipartite graph can be 2-colored: vertices split into two sets where all edges go between sets (no edges within a set). BFS-based 2-coloring checks bipartiteness in O(V+E). Applications: job matching (workers and jobs are two sets), recommendation systems (users and items), scheduling (tasks and time slots), and the fundamental theorem that a graph is bipartite if and only if it contains no odd-length cycles.

🎯 Learning Outcomes

  • • Implement BFS 2-coloring: assign alternating colors to adjacent vertices
  • • Detect non-bipartiteness: same color on adjacent vertices during BFS
  • • Handle disconnected graphs: run BFS from every uncolored vertex
  • • Understand the bipartite-iff-no-odd-cycles theorem
  • • Apply to matching problems (König's theorem: max matching = min vertex cover in bipartite graphs)
  • Code Example

    #![allow(clippy::all)]
    //! # Bipartite Check
    pub fn is_bipartite(n: usize, edges: &[(usize, usize)]) -> bool {
        let mut adj = vec![vec![]; n];
        for &(u, v) in edges {
            adj[u].push(v);
            adj[v].push(u);
        }
        let mut color = vec![None; n];
        fn bfs(start: usize, adj: &[Vec<usize>], color: &mut [Option<bool>]) -> bool {
            use std::collections::VecDeque;
            let mut q = VecDeque::new();
            q.push_back(start);
            color[start] = Some(true);
            while let Some(u) = q.pop_front() {
                for &v in &adj[u] {
                    if color[v].is_none() {
                        color[v] = Some(!color[u].unwrap());
                        q.push_back(v);
                    } else if color[v] == color[u] {
                        return false;
                    }
                }
            }
            true
        }
        for v in 0..n {
            if color[v].is_none() && !bfs(v, &adj, &mut color) {
                return false;
            }
        }
        true
    }
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_bipartite() {
            assert!(is_bipartite(4, &[(0, 1), (1, 2), (2, 3), (3, 0)]));
        }
        #[test]
        fn test_not_bipartite() {
            assert!(!is_bipartite(3, &[(0, 1), (1, 2), (2, 0)]));
        }
    }

    Key Differences

  • BFS queue: Rust uses VecDeque<usize> (FIFO); OCaml uses Queue.t — equivalent data structures.
  • Color representation: Rust uses Option<bool> (uncolored / colored); OCaml uses int option or bool option similarly.
  • Disconnected graphs: Both check all vertices to handle disconnected graphs — the for v in 0..n outer loop is identical.
  • Matching connection: Bipartite check is a prerequisite for maximum bipartite matching (Hopcroft-Karp); both languages use the same foundation.
  • OCaml Approach

    OCaml implements BFS with Queue.t and a color: bool option array. The Queue.add / Queue.pop pattern drives the BFS. OCaml's Option.map applies color flipping functionally. The Ocamlgraph library provides Coloring.check_bipartite. OCaml's for v in adj.(u) do ... is idiomatic for adjacency list traversal.

    Full Source

    #![allow(clippy::all)]
    //! # Bipartite Check
    pub fn is_bipartite(n: usize, edges: &[(usize, usize)]) -> bool {
        let mut adj = vec![vec![]; n];
        for &(u, v) in edges {
            adj[u].push(v);
            adj[v].push(u);
        }
        let mut color = vec![None; n];
        fn bfs(start: usize, adj: &[Vec<usize>], color: &mut [Option<bool>]) -> bool {
            use std::collections::VecDeque;
            let mut q = VecDeque::new();
            q.push_back(start);
            color[start] = Some(true);
            while let Some(u) = q.pop_front() {
                for &v in &adj[u] {
                    if color[v].is_none() {
                        color[v] = Some(!color[u].unwrap());
                        q.push_back(v);
                    } else if color[v] == color[u] {
                        return false;
                    }
                }
            }
            true
        }
        for v in 0..n {
            if color[v].is_none() && !bfs(v, &adj, &mut color) {
                return false;
            }
        }
        true
    }
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_bipartite() {
            assert!(is_bipartite(4, &[(0, 1), (1, 2), (2, 3), (3, 0)]));
        }
        #[test]
        fn test_not_bipartite() {
            assert!(!is_bipartite(3, &[(0, 1), (1, 2), (2, 0)]));
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_bipartite() {
            assert!(is_bipartite(4, &[(0, 1), (1, 2), (2, 3), (3, 0)]));
        }
        #[test]
        fn test_not_bipartite() {
            assert!(!is_bipartite(3, &[(0, 1), (1, 2), (2, 0)]));
        }
    }

    Deep Comparison

    OCaml vs Rust: Bipartite Check

    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 bipartite_partition(n, edges) -> Option<(Vec<usize>, Vec<usize>)> that returns the two vertex sets if bipartite, or None if not.
  • Find the shortest odd cycle in a non-bipartite graph using BFS level tracking — the cycle length is the graph's "odd girth."
  • Implement maximum bipartite matching using the augmenting path algorithm (Hungarian / Hopcroft-Karp), building on the bipartite check.
  • Open Source Repos