|
| 1 | +//! This example demonstrates the experimental complexity analysis of the Kuhn-Munkres algorithm. |
| 2 | +//! |
| 3 | +//! The Kuhn-Munkres algorithm (also known as the Hungarian algorithm) is documented to have |
| 4 | +//! O(n³) time complexity, where n is the size of the input matrix. This example runs the |
| 5 | +//! algorithm with various input sizes and measures the execution time to experimentally |
| 6 | +//! verify this complexity. |
| 7 | +
|
| 8 | +use pathfinding::prelude::{Matrix, kuhn_munkres}; |
| 9 | +use rand::{Rng, SeedableRng}; |
| 10 | +use rand_xorshift::XorShiftRng; |
| 11 | +use std::time::Instant; |
| 12 | + |
| 13 | +/// Test sizes to use for complexity analysis |
| 14 | +const TEST_SIZES: &[usize] = &[5, 10, 20, 30, 40, 50, 60, 80, 100, 120, 150, 200]; |
| 15 | + |
| 16 | +/// Number of iterations to average for each size |
| 17 | +const ITERATIONS: usize = 20; |
| 18 | + |
| 19 | +/// Structure to hold timing results for a specific size |
| 20 | +struct TimingResult { |
| 21 | + size: usize, |
| 22 | + avg_time_micros: f64, |
| 23 | +} |
| 24 | + |
| 25 | +fn main() { |
| 26 | + println!("Kuhn-Munkres Algorithm - Experimental Complexity Analysis"); |
| 27 | + println!("=========================================================\n"); |
| 28 | + println!("Testing with matrix sizes: {TEST_SIZES:?}"); |
| 29 | + println!("Iterations per size: {ITERATIONS}\n"); |
| 30 | + |
| 31 | + let mut results = Vec::new(); |
| 32 | + |
| 33 | + // Run tests for each size |
| 34 | + for &size in TEST_SIZES { |
| 35 | + println!("Testing size {size}x{size}..."); |
| 36 | + |
| 37 | + let mut total_time_micros = 0u128; |
| 38 | + |
| 39 | + // Use a fixed seed for reproducibility |
| 40 | + let mut rng = XorShiftRng::from_seed([ |
| 41 | + 3, 42, 93, 129, 1, 85, 72, 42, 84, 23, 95, 212, 253, 10, 4, 2, |
| 42 | + ]); |
| 43 | + |
| 44 | + for iteration in 0..ITERATIONS { |
| 45 | + // Generate a random square matrix with weights between 1 and 100 |
| 46 | + let weights = Matrix::square_from_vec( |
| 47 | + (0..(size * size)) |
| 48 | + .map(|_| rng.random_range(1..=100)) |
| 49 | + .collect::<Vec<_>>(), |
| 50 | + ) |
| 51 | + .unwrap(); |
| 52 | + |
| 53 | + // Measure execution time |
| 54 | + let start = Instant::now(); |
| 55 | + let _result = kuhn_munkres(&weights); |
| 56 | + let elapsed = start.elapsed(); |
| 57 | + |
| 58 | + total_time_micros += elapsed.as_micros(); |
| 59 | + |
| 60 | + if iteration == 0 { |
| 61 | + println!(" First iteration: {elapsed:.2?}"); |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + #[expect(clippy::cast_precision_loss)] |
| 66 | + let avg_time_micros = total_time_micros as f64 / ITERATIONS as f64; |
| 67 | + println!(" Average time: {avg_time_micros:.2} μs\n"); |
| 68 | + |
| 69 | + results.push(TimingResult { |
| 70 | + size, |
| 71 | + avg_time_micros, |
| 72 | + }); |
| 73 | + } |
| 74 | + |
| 75 | + // Analyze complexity |
| 76 | + println!("\nComplexity Analysis"); |
| 77 | + println!("===================\n"); |
| 78 | + println!("Expected complexity: O(n³)"); |
| 79 | + println!( |
| 80 | + "{:>6} {:>15} {:>15} {:>15}", |
| 81 | + "Size", "Time (μs)", "n³", "Time/n³" |
| 82 | + ); |
| 83 | + println!("{}", "-".repeat(54)); |
| 84 | + |
| 85 | + for result in &results { |
| 86 | + #[expect(clippy::cast_precision_loss)] |
| 87 | + let n_cubed = (result.size.pow(3)) as f64; |
| 88 | + let ratio = result.avg_time_micros / n_cubed; |
| 89 | + println!( |
| 90 | + "{:>6} {:>15.2} {:>15.0} {:>15.6}", |
| 91 | + result.size, result.avg_time_micros, n_cubed, ratio |
| 92 | + ); |
| 93 | + } |
| 94 | + |
| 95 | + // Calculate growth rates between consecutive sizes |
| 96 | + println!("\nGrowth Rate Analysis"); |
| 97 | + println!("====================\n"); |
| 98 | + println!("If the algorithm is O(n³), when size doubles, time should increase by ~8x (2³ = 8)"); |
| 99 | + println!( |
| 100 | + "{:>10} {:>15} {:>15}", |
| 101 | + "Size Ratio", "Time Ratio", "Expected" |
| 102 | + ); |
| 103 | + println!("{}", "-".repeat(42)); |
| 104 | + |
| 105 | + for i in 1..results.len() { |
| 106 | + #[expect(clippy::cast_precision_loss)] |
| 107 | + let size_ratio = results[i].size as f64 / results[i - 1].size as f64; |
| 108 | + let time_ratio = results[i].avg_time_micros / results[i - 1].avg_time_micros; |
| 109 | + let expected_ratio = size_ratio.powi(3); |
| 110 | + println!("{size_ratio:>10.2}x {time_ratio:>14.2}x {expected_ratio:>14.2}x"); |
| 111 | + } |
| 112 | + |
| 113 | + println!("\nConclusion"); |
| 114 | + println!("=========="); |
| 115 | + println!("The Time/n³ ratio should remain relatively constant if the algorithm is O(n³)."); |
| 116 | + println!("The actual time ratio should be close to the expected ratio for size doublings."); |
| 117 | +} |
0 commit comments