diff --git a/dynamic-programming/notes.md b/dynamic-programming/notes.md index 3a21112746d655eca19b30a36552b35a74890dc9..c2821f3c1c89f1164bbce957e628bb7004047549 100644 --- a/dynamic-programming/notes.md +++ b/dynamic-programming/notes.md @@ -54,35 +54,35 @@ Problem: Subject to an integer weight limit, what choice of items (allowing repe ## DAG * Edges (actions): - * … or - * … + * Take one of the items or + * Take one kg of nothing. * Vertices (situations): - * … + * How much weight have we put into the backpack so far? * Edge weights: - * … + * The value of the item being taken * Topological order: - * … + * Increasing order : 0 → 1 → … → `weightLimit` (assuming that no items have nonpositive weight) * Goal: - * … + * Find a longest path from 0 to `weightLimit` ## Backpointer Class * Information for the final result: - * … + * What item did we take? * Information to go back: - * … + * What was the weight of the item taken? (this is redundant with the item taken) * Information to compare quality: - * … + * What is the *total* value of all of the items taken so far? ## Choosing a Backpointer * Exhaustive search: - * Generate …. - * Check that …. + * Generate items from the list or else 1 kg of nothing, but make sure that we don't try to go back to a negative total weight. + * Check that the item maximizes the total value (the previous total value plus the value of the item used to go back). ## Example -* Item Z …. +* Item Z weighs 1 kg and is worth $0. * Item A weighs 3 kg and is worth $10. * Item B weighs 4 kg and is worth $14. @@ -91,20 +91,20 @@ Problem: Subject to an integer weight limit, what choice of items (allowing repe Weight (kg) Item Total Value (Back to Weight) ----------- ------ ----------- ---------------- 0 [none] $0 ⊥ - 1 Item … … … - 2 Item … … … - 3 Item … … … - 4 Item … … … - 5 Item … … … - 6 Item … … … - 7 Item … … … - 8 Item … … … - 9 Item … … … - 10 Item … … … + 1 Item Z 0 0 + 2 Item Z 0 1 + 3 Item A 10 0 + 4 Item B 14 0 + 5 Item Z 14 4 + 6 Item A 20 3 + 7 Item A 24 4 + 8 Item B 28 4 + 9 Item A 30 6 + 10 Item A 34 7 Reversed Path ---- - 10 ← (Item …) ← … + 10 ← (Item A) ← 7 ← (Item A) ← 4 ← (Item B) ← 0 -------------------------------------------------------------------------------- diff --git a/dynamic-programming/src/features/knapsack/solver.js b/dynamic-programming/src/features/knapsack/solver.js index 495b9e198445fb4ff055bf7ccd9c339b1b63a83f..02ea1b16823594b70ac85f954d2a6e5fa88dfc49 100644 --- a/dynamic-programming/src/features/knapsack/solver.js +++ b/dynamic-programming/src/features/knapsack/solver.js @@ -1,17 +1,44 @@ import { Item } from './items.js'; +const KILOGRAM_OF_NOTHING = new Item(1, 0); + class Backpointer { - constructor() { - // TODO: stub + constructor(item, totalValue) { + this.item = item; + this.totalValue = totalValue; } } function chooseBackpointer(items, backpointers) { const currentWeight = backpointers.length; - // TODO: stub + let best = new Backpointer(KILOGRAM_OF_NOTHING, backpointers[currentWeight - 1].totalValue); + for (const item of items) { + const previousWeight = currentWeight - item.weight; + if (previousWeight >= 0) { + const candidate = new Backpointer( + item, + backpointers[previousWeight].totalValue + item.value, + ); + if (candidate.totalValue > best.totalValue) { + best = candidate; + } + } + } + return best; } export function chooseItems(items, weightLimit) { console.assert(Number.isInteger(weightLimit), `Tried to choose items with a noninteger weight limit ${weightLimit}.`); - return []; // TODO: stub + const backpointers = [new Backpointer(undefined, 0)]; + while (backpointers.length <= weightLimit) { + backpointers.push(chooseBackpointer(items, backpointers)); + } + const reversedPath = []; + for (let weight = weightLimit; weight > 0; weight -= backpointers[weight].item.weight) { + const item = backpointers[weight].item; + if (item !== KILOGRAM_OF_NOTHING) { + reversedPath.push(item); + } + } + return reversedPath.reverse(); }