mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2025-07-07 09:54:53 +08:00
Add knapsack problem.
This commit is contained in:
@ -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)
|
||||
@ -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
|
||||
|
@ -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
|
||||
@ -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
|
||||
@ -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.
|
||||
@ -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) => {
|
||||
|
@ -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}`;
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user