Skip to content

Commit 2b783da

Browse files
feat: add kuhn_munkres_complexity example to experimentally measure algorithm complexity
Co-authored-by: samueltardieu <[email protected]>
1 parent 5be0140 commit 2b783da

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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

Comments
 (0)