Skip to content

Commit

Permalink
Add knapsack problem.
Browse files Browse the repository at this point in the history
  • Loading branch information
trekhleb committed Apr 30, 2018
1 parent d20d0c8 commit 0ce85ce
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 33 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
### Algorithms by Paradigm

* **Greedy**
* [Unbound Knapsack Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/knapsack-problem)
* **Divide and Conquer**
* [Euclidean Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/math/euclidean-algorithm) - calculate the Greatest Common Divisor (GCD)
* [Permutations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/permutations) (with and without repetitions)
Expand All @@ -95,7 +96,7 @@
* [Longest Common Substring](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/longest-common-substring)
* [Longest Increasing subsequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/longest-increasing-subsequence)
* [Shortest Common Supersequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/shortest-common-supersequence)
* [Knapsack Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/knapsack-problem)
* [0/1 Knapsack Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/knapsack-problem)
* Maximum subarray
* Maximum sum path
* Integer Partition
Expand Down
57 changes: 46 additions & 11 deletions src/algorithms/sets/knapsack-problem/Knapsack.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,9 @@ export default class Knapsack {
this.selectedItems = [];
this.weightLimit = weightLimit;
this.possibleItems = possibleItems;
// We do two sorts because in case of equal weights but different values
// we need to take the most valuable items first.
this.sortPossibleItemsByValue();
this.sortPossibleItemsByWeight();
}

sortPossibleItemsByWeight() {
// Sort possible items by their weight.
// We need them to be sorted in order to solve knapsack problem using
// Dynamic Programming approach.
this.possibleItems = new MergeSort({
/**
* @var KnapsackItem itemA
Expand All @@ -35,9 +28,6 @@ export default class Knapsack {
}

sortPossibleItemsByValue() {
// Sort possible items by their weight.
// We need them to be sorted in order to solve knapsack problem using
// Dynamic Programming approach.
this.possibleItems = new MergeSort({
/**
* @var KnapsackItem itemA
Expand All @@ -53,8 +43,30 @@ export default class Knapsack {
}).sort(this.possibleItems);
}

// Solve 0/1 knapsack problem using dynamic programming.
sortPossibleItemsByValuePerWeightRatio() {
this.possibleItems = new MergeSort({
/**
* @var KnapsackItem itemA
* @var KnapsackItem itemB
*/
compareCallback: (itemA, itemB) => {
if (itemA.valuePerWeightRatio === itemB.valuePerWeightRatio) {
return 0;
}

return itemA.valuePerWeightRatio > itemB.valuePerWeightRatio ? -1 : 1;
},
}).sort(this.possibleItems);
}

// Solve 0/1 knapsack problem
// Dynamic Programming approach.
solveZeroOneKnapsackProblem() {
// We do two sorts because in case of equal weights but different values
// we need to take the most valuable items first.
this.sortPossibleItemsByValue();
this.sortPossibleItemsByWeight();

this.selectedItems = [];

// Create knapsack values matrix.
Expand Down Expand Up @@ -138,6 +150,29 @@ export default class Knapsack {
}
}


// Solve unbounded knapsack problem.
// Greedy approach.
solveUnboundedKnapsackProblem() {
this.sortPossibleItemsByValue();
this.sortPossibleItemsByValuePerWeightRatio();

for (let itemIndex = 0; itemIndex < this.possibleItems.length; itemIndex += 1) {
if (this.totalWeight < this.weightLimit) {
const currentItem = this.possibleItems[itemIndex];

// Detect how much of current items we can push to knapsack.
const availableWeight = this.weightLimit - this.totalWeight;
const maxPossibleItemsCount = Math.floor(availableWeight / currentItem.weight);

if (maxPossibleItemsCount) {
currentItem.quantity = maxPossibleItemsCount;
this.selectedItems.push(currentItem);
}
}
}
}

get totalValue() {
/** @var {KnapsackItem} item */
return this.selectedItems.reduce((accumulator, item) => {
Expand Down
6 changes: 6 additions & 0 deletions src/algorithms/sets/knapsack-problem/KnapsackItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export default class KnapsackItem {
return this.weight * this.quantity;
}

// This coefficient shows how valuable the 1 unit of weight is
// for current item.
get valuePerWeightRatio() {
return this.value / this.weight;
}

toString() {
return `v${this.value} w${this.weight} x ${this.quantity}`;
}
Expand Down
23 changes: 23 additions & 0 deletions src/algorithms/sets/knapsack-problem/__test__/Knapsack.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,27 @@ describe('Knapsack', () => {
expect(knapsack.selectedItems[1].toString()).toBe('v5 w1 x 1');
expect(knapsack.selectedItems[2].toString()).toBe('v7 w1 x 1');
});

it('should solve unbound knapsack problem', () => {
const possibleKnapsackItems = [
new KnapsackItem({ value: 84, weight: 7 }), // v/w ratio is 12
new KnapsackItem({ value: 5, weight: 2 }), // v/w ratio is 2.5
new KnapsackItem({ value: 12, weight: 3 }), // v/w ratio is 4
new KnapsackItem({ value: 10, weight: 1 }), // v/w ratio is 10
new KnapsackItem({ value: 20, weight: 2 }), // v/w ratio is 10
];

const maxKnapsackWeight = 17;

const knapsack = new Knapsack(possibleKnapsackItems, maxKnapsackWeight);

knapsack.solveUnboundedKnapsackProblem();

expect(knapsack.totalValue).toBe(84 + 84 + 20 + 10);
expect(knapsack.totalWeight).toBe(17);
expect(knapsack.selectedItems.length).toBe(3);
expect(knapsack.selectedItems[0].toString()).toBe('v84 w7 x 2');
expect(knapsack.selectedItems[1].toString()).toBe('v20 w2 x 1');
expect(knapsack.selectedItems[2].toString()).toBe('v10 w1 x 1');
});
});
45 changes: 24 additions & 21 deletions src/algorithms/sets/knapsack-problem/__test__/KnapsackItem.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,34 @@ import KnapsackItem from '../KnapsackItem';

describe('KnapsackItem', () => {
it('should create knapsack item and count its total weight and value', () => {
const item1 = new KnapsackItem({ value: 3, weight: 2 });
const knapsackItem = new KnapsackItem({ value: 3, weight: 2 });

expect(item1.value).toBe(3);
expect(item1.weight).toBe(2);
expect(item1.quantity).toBe(1);
expect(item1.toString()).toBe('v3 w2 x 1');
expect(item1.totalValue).toBe(3);
expect(item1.totalWeight).toBe(2);
expect(knapsackItem.value).toBe(3);
expect(knapsackItem.weight).toBe(2);
expect(knapsackItem.quantity).toBe(1);
expect(knapsackItem.valuePerWeightRatio).toBe(1.5);
expect(knapsackItem.toString()).toBe('v3 w2 x 1');
expect(knapsackItem.totalValue).toBe(3);
expect(knapsackItem.totalWeight).toBe(2);

item1.quantity = 0;
knapsackItem.quantity = 0;

expect(item1.value).toBe(3);
expect(item1.weight).toBe(2);
expect(item1.quantity).toBe(0);
expect(item1.toString()).toBe('v3 w2 x 0');
expect(item1.totalValue).toBe(0);
expect(item1.totalWeight).toBe(0);
expect(knapsackItem.value).toBe(3);
expect(knapsackItem.weight).toBe(2);
expect(knapsackItem.quantity).toBe(0);
expect(knapsackItem.valuePerWeightRatio).toBe(1.5);
expect(knapsackItem.toString()).toBe('v3 w2 x 0');
expect(knapsackItem.totalValue).toBe(0);
expect(knapsackItem.totalWeight).toBe(0);

item1.quantity = 2;
knapsackItem.quantity = 2;

expect(item1.value).toBe(3);
expect(item1.weight).toBe(2);
expect(item1.quantity).toBe(2);
expect(item1.toString()).toBe('v3 w2 x 2');
expect(item1.totalValue).toBe(6);
expect(item1.totalWeight).toBe(4);
expect(knapsackItem.value).toBe(3);
expect(knapsackItem.weight).toBe(2);
expect(knapsackItem.quantity).toBe(2);
expect(knapsackItem.valuePerWeightRatio).toBe(1.5);
expect(knapsackItem.toString()).toBe('v3 w2 x 2');
expect(knapsackItem.totalValue).toBe(6);
expect(knapsackItem.totalWeight).toBe(4);
});
});

0 comments on commit 0ce85ce

Please sign in to comment.