CSCA 5414: Greedy Algorithms

Rod cutting is shown as a canonical example of using DP in a recursive and then memoized setting (a third example recovering the data from the table is elided for brevity).

#![allow(unused)]
fn main() {
// Naive attempt to implement the recurrence (see dp_rodcutting.ipynb)
fn max_revenue_recursive(len: i32, sizes: &Vec<i32>, prices: &Vec<f64>) -> i32 {
  if len == 0 {
    return 0;
  }
  if len < 0 {
    return -100_000_000;
  }
  let k = sizes.len();
  assert_eq!(prices.len(), k);
  let mut option_values = vec![];
  for i in 0..k {
    option_values.push(prices[i] + max_revenue_recursive(
      len-sizes[i], sizes, prices
    ) as f64);
  }
  option_values.push(0.);
  option_values.into_iter().reduce(f64::max).unwrap() as i32
}

#[test]
fn examples() {
    let sizes = vec![1, 3, 5, 10, 30, 50, 75];
    let prices = vec![0.1, 0.2, 0.4, 0.9, 3.1, 5.1, 8.2];
    assert_eq!(3.0, max_revenue_recursive(30, &sizes, &prices).round());
    assert_eq!(5.0, max_revenue_recursive(50, &sizes, &prices).round());
    assert_eq!(33.0, max_revenue_recursive(300, &sizes, &prices).round());
}
}

Assumptions

  • L is a positive usize
  • sizes and prices will always have the same length

Fining an optimal substructure

Given some length , we say

  1. A cut of pays revenue.
  2. Optimally cut remaining

Example recurrence relations

Example base cases

  • whenever . If no rod to cut then nothing to do.
  • whenever . If there is negative rod to cut, we will "punish" the cutter infinitely.
#![allow(unused)]
fn main() {
// Memoized version
fn max_revenue_memoize(len: i32, sizes: &Vec<i32>, prices: &Vec<f64>) -> f64 {
    let n = len as usize;
    let k = sizes.len();
    assert_eq!(prices.len(), k);

    let mut table = vec![0.; n + 1];
    let mut vals = vec![];

    for i in 1..n + 1 {
        for j in 0..k {
            let i = i as i32;
            if i - sizes[j] >= 0 {
                let idx = (i - sizes[j]) as usize;
                vals.push(prices[j] + table[idx] as f64);
                vals.push(0.);
                table[i as usize] = vals
                  .clone()
                  .into_iter()
                  .reduce(f64::max)
                  .unwrap()
            }
        }
    }
    table[n]
}

#[test]
fn examples() {
    let sizes = vec![1, 3, 5, 10, 30, 50, 75];
    let prices = vec![0.1, 0.2, 0.4, 0.9, 3.1, 5.1, 8.2];
    assert_eq!(3.0, max_revenue_memoize(30, &sizes, &prices).round());
    assert_eq!(5.0, max_revenue_memoize(50, &sizes, &prices).round());
    assert_eq!(33.0, max_revenue_memoize(300, &sizes, &prices).round());
}
}

When no optimal substructure exists

Noting some conditions of the well-known knapsack problem, where is weight.

Value:

An alternate example of non-optimal substructure is path-finding, where movement decisions may be disjoint. The sequencing of finding optimal substructures thus becomes the driving factor.