785-knapsack-01 — 0/1 Knapsack Problem
Tutorial Video
Text description (accessibility)
This video demonstrates the "785-knapsack-01 — 0/1 Knapsack Problem" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. The 0/1 knapsack problem is a foundational combinatorial optimization problem: given items with weights and values and a capacity-limited bag, select items to maximize total value without exceeding capacity. Key difference from OCaml: 1. **Mutability**: Rust's `vec![vec![0; capacity + 1]; n + 1]` with `dp[i][w] = ...` is imperative; OCaml's `for` loop equivalent is similar but uses `Array.set dp.(i).(w) val`.
Tutorial
The Problem
The 0/1 knapsack problem is a foundational combinatorial optimization problem: given items with weights and values and a capacity-limited bag, select items to maximize total value without exceeding capacity. "0/1" means each item is either taken or not (no fractions). It appears in resource allocation, project portfolio selection, cargo loading, and is a building block for more complex optimization problems. The DP solution runs in O(n × W) time, making large instances tractable.
🎯 Learning Outcomes
dp[i][w] = max value using first i items with capacity wdp[i][w] = max(dp[i-1][w], dp[i-1][w - weight[i]] + value[i])Code Example
#![allow(clippy::all)]
//! # 0/1 Knapsack Problem
//!
//! Classic dynamic programming problem.
/// Solve 0/1 knapsack problem
pub fn knapsack(weights: &[usize], values: &[usize], capacity: usize) -> usize {
let n = weights.len();
let mut dp = vec![vec![0; capacity + 1]; n + 1];
for i in 1..=n {
for w in 0..=capacity {
if weights[i - 1] <= w {
dp[i][w] = dp[i - 1][w].max(dp[i - 1][w - weights[i - 1]] + values[i - 1]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
dp[n][capacity]
}
/// Space-optimized version
pub fn knapsack_optimized(weights: &[usize], values: &[usize], capacity: usize) -> usize {
let mut dp = vec![0; capacity + 1];
for i in 0..weights.len() {
for w in (weights[i]..=capacity).rev() {
dp[w] = dp[w].max(dp[w - weights[i]] + values[i]);
}
}
dp[capacity]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_knapsack() {
let weights = [1, 2, 3];
let values = [6, 10, 12];
assert_eq!(knapsack(&weights, &values, 5), 22);
}
#[test]
fn test_knapsack_optimized() {
let weights = [1, 2, 3];
let values = [6, 10, 12];
assert_eq!(knapsack_optimized(&weights, &values, 5), 22);
}
#[test]
fn test_empty() {
assert_eq!(knapsack(&[], &[], 10), 0);
}
#[test]
fn test_no_capacity() {
let weights = [1, 2];
let values = [10, 20];
assert_eq!(knapsack(&weights, &values, 0), 0);
}
}Key Differences
vec![vec![0; capacity + 1]; n + 1] with dp[i][w] = ... is imperative; OCaml's for loop equivalent is similar but uses Array.set dp.(i).(w) val.dp by comparing dp[i][w] with dp[i-1][w]; OCaml uses the same technique.Vec bounds-checks by default; OCaml's Array.unsafe_get skips checks for performance in tight DP loops.OCaml Approach
OCaml's functional style uses Array.init for DP table construction and Array.fold_left for optimization. The functional approach with Array.init (n+1) (fun i -> Array.init (cap+1) (fun w -> ...)) creates the full table lazily but requires a fix-point or explicit memoization. The imperative style with for loops and Array.set is idiomatic for DP in OCaml despite the functional aesthetic.
Full Source
#![allow(clippy::all)]
//! # 0/1 Knapsack Problem
//!
//! Classic dynamic programming problem.
/// Solve 0/1 knapsack problem
pub fn knapsack(weights: &[usize], values: &[usize], capacity: usize) -> usize {
let n = weights.len();
let mut dp = vec![vec![0; capacity + 1]; n + 1];
for i in 1..=n {
for w in 0..=capacity {
if weights[i - 1] <= w {
dp[i][w] = dp[i - 1][w].max(dp[i - 1][w - weights[i - 1]] + values[i - 1]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
dp[n][capacity]
}
/// Space-optimized version
pub fn knapsack_optimized(weights: &[usize], values: &[usize], capacity: usize) -> usize {
let mut dp = vec![0; capacity + 1];
for i in 0..weights.len() {
for w in (weights[i]..=capacity).rev() {
dp[w] = dp[w].max(dp[w - weights[i]] + values[i]);
}
}
dp[capacity]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_knapsack() {
let weights = [1, 2, 3];
let values = [6, 10, 12];
assert_eq!(knapsack(&weights, &values, 5), 22);
}
#[test]
fn test_knapsack_optimized() {
let weights = [1, 2, 3];
let values = [6, 10, 12];
assert_eq!(knapsack_optimized(&weights, &values, 5), 22);
}
#[test]
fn test_empty() {
assert_eq!(knapsack(&[], &[], 10), 0);
}
#[test]
fn test_no_capacity() {
let weights = [1, 2];
let values = [10, 20];
assert_eq!(knapsack(&weights, &values, 0), 0);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_knapsack() {
let weights = [1, 2, 3];
let values = [6, 10, 12];
assert_eq!(knapsack(&weights, &values, 5), 22);
}
#[test]
fn test_knapsack_optimized() {
let weights = [1, 2, 3];
let values = [6, 10, 12];
assert_eq!(knapsack_optimized(&weights, &values, 5), 22);
}
#[test]
fn test_empty() {
assert_eq!(knapsack(&[], &[], 10), 0);
}
#[test]
fn test_no_capacity() {
let weights = [1, 2];
let values = [10, 20];
assert_eq!(knapsack(&weights, &values, 0), 0);
}
}
Deep Comparison
OCaml vs Rust: Knapsack 01
Overview
See the example.rs and example.ml files for detailed implementations.
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Type system | Hindley-Milner | Ownership + traits |
| Memory | GC | Zero-cost abstractions |
| Mutability | Explicit ref | mut keyword |
| Error handling | Option/Result | Result<T, E> |
See README.md for detailed comparison.
Exercises
knapsack to also return the indices of the chosen items by backtracking through the DP table.