Add knapsack problem.

This commit is contained in:
Oleksii Trekhleb
2018-04-30 14:08:14 +03:00
parent d20d0c8d4f
commit 0ce85ce15f
5 changed files with 101 additions and 33 deletions

View File

@ -80,6 +80,7 @@
### Algorithms by Paradigm ### Algorithms by Paradigm
* **Greedy** * **Greedy**
* [Unbound Knapsack Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/knapsack-problem)
* **Divide and Conquer** * **Divide and Conquer**
* [Euclidean Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/math/euclidean-algorithm) - calculate the Greatest Common Divisor (GCD) * [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) * [Permutations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/permutations) (with and without repetitions)
@ -95,7 +96,7 @@
* [Longest Common Substring](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/longest-common-substring) * [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) * [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) * [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 subarray
* Maximum sum path * Maximum sum path
* Integer Partition * Integer Partition

View File

@ -9,16 +9,9 @@ export default class Knapsack {
this.selectedItems = []; this.selectedItems = [];
this.weightLimit = weightLimit; this.weightLimit = weightLimit;
this.possibleItems = possibleItems; 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() { 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({ this.possibleItems = new MergeSort({
/** /**
* @var KnapsackItem itemA * @var KnapsackItem itemA
@ -35,9 +28,6 @@ export default class Knapsack {
} }
sortPossibleItemsByValue() { 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({ this.possibleItems = new MergeSort({
/** /**
* @var KnapsackItem itemA * @var KnapsackItem itemA
@ -53,8 +43,30 @@ export default class Knapsack {
}).sort(this.possibleItems); }).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() { 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 = []; this.selectedItems = [];
// Create knapsack values matrix. // Create knapsack values matrix.
@ -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() { get totalValue() {
/** @var {KnapsackItem} item */ /** @var {KnapsackItem} item */
return this.selectedItems.reduce((accumulator, item) => { return this.selectedItems.reduce((accumulator, item) => {

View File

@ -21,6 +21,12 @@ export default class KnapsackItem {
return this.weight * this.quantity; 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() { toString() {
return `v${this.value} w${this.weight} x ${this.quantity}`; return `v${this.value} w${this.weight} x ${this.quantity}`;
} }

View File

@ -86,4 +86,27 @@ describe('Knapsack', () => {
expect(knapsack.selectedItems[1].toString()).toBe('v5 w1 x 1'); expect(knapsack.selectedItems[1].toString()).toBe('v5 w1 x 1');
expect(knapsack.selectedItems[2].toString()).toBe('v7 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');
});
}); });

View File

@ -2,31 +2,34 @@ import KnapsackItem from '../KnapsackItem';
describe('KnapsackItem', () => { describe('KnapsackItem', () => {
it('should create knapsack item and count its total weight and value', () => { 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(knapsackItem.value).toBe(3);
expect(item1.weight).toBe(2); expect(knapsackItem.weight).toBe(2);
expect(item1.quantity).toBe(1); expect(knapsackItem.quantity).toBe(1);
expect(item1.toString()).toBe('v3 w2 x 1'); expect(knapsackItem.valuePerWeightRatio).toBe(1.5);
expect(item1.totalValue).toBe(3); expect(knapsackItem.toString()).toBe('v3 w2 x 1');
expect(item1.totalWeight).toBe(2); expect(knapsackItem.totalValue).toBe(3);
expect(knapsackItem.totalWeight).toBe(2);
item1.quantity = 0; knapsackItem.quantity = 0;
expect(item1.value).toBe(3); expect(knapsackItem.value).toBe(3);
expect(item1.weight).toBe(2); expect(knapsackItem.weight).toBe(2);
expect(item1.quantity).toBe(0); expect(knapsackItem.quantity).toBe(0);
expect(item1.toString()).toBe('v3 w2 x 0'); expect(knapsackItem.valuePerWeightRatio).toBe(1.5);
expect(item1.totalValue).toBe(0); expect(knapsackItem.toString()).toBe('v3 w2 x 0');
expect(item1.totalWeight).toBe(0); expect(knapsackItem.totalValue).toBe(0);
expect(knapsackItem.totalWeight).toBe(0);
item1.quantity = 2; knapsackItem.quantity = 2;
expect(item1.value).toBe(3); expect(knapsackItem.value).toBe(3);
expect(item1.weight).toBe(2); expect(knapsackItem.weight).toBe(2);
expect(item1.quantity).toBe(2); expect(knapsackItem.quantity).toBe(2);
expect(item1.toString()).toBe('v3 w2 x 2'); expect(knapsackItem.valuePerWeightRatio).toBe(1.5);
expect(item1.totalValue).toBe(6); expect(knapsackItem.toString()).toBe('v3 w2 x 2');
expect(item1.totalWeight).toBe(4); expect(knapsackItem.totalValue).toBe(6);
expect(knapsackItem.totalWeight).toBe(4);
}); });
}); });