mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2026-03-13 08:51:02 +08:00
Add knapsack problem.
This commit is contained in:
154
src/algorithms/sets/knapsack-problem/Knapsack.js
Normal file
154
src/algorithms/sets/knapsack-problem/Knapsack.js
Normal file
@@ -0,0 +1,154 @@
|
||||
import MergeSort from '../../sorting/merge-sort/MergeSort';
|
||||
|
||||
export default class Knapsack {
|
||||
/**
|
||||
* @param {KnapsackItem[]} possibleItems
|
||||
* @param {number} weightLimit
|
||||
*/
|
||||
constructor(possibleItems, weightLimit) {
|
||||
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
|
||||
* @var KnapsackItem itemB
|
||||
*/
|
||||
compareCallback: (itemA, itemB) => {
|
||||
if (itemA.weight === itemB.weight) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return itemA.weight < itemB.weight ? -1 : 1;
|
||||
},
|
||||
}).sort(this.possibleItems);
|
||||
}
|
||||
|
||||
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
|
||||
* @var KnapsackItem itemB
|
||||
*/
|
||||
compareCallback: (itemA, itemB) => {
|
||||
if (itemA.value === itemB.value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return itemA.value > itemB.value ? -1 : 1;
|
||||
},
|
||||
}).sort(this.possibleItems);
|
||||
}
|
||||
|
||||
// Solve 0/1 knapsack problem using dynamic programming.
|
||||
solveZeroOneKnapsackProblem() {
|
||||
this.selectedItems = [];
|
||||
|
||||
// Create knapsack values matrix.
|
||||
const numberOfRows = this.possibleItems.length;
|
||||
const numberOfColumns = this.weightLimit;
|
||||
const knapsackMatrix = Array(numberOfRows).fill(null).map(() => {
|
||||
return Array(numberOfColumns + 1).fill(null);
|
||||
});
|
||||
|
||||
// Fill the first column with zeros since it would mean that there is
|
||||
// no items we can add to knapsack in case if weight limitation is zero.
|
||||
for (let itemIndex = 0; itemIndex < this.possibleItems.length; itemIndex += 1) {
|
||||
knapsackMatrix[itemIndex][0] = 0;
|
||||
}
|
||||
|
||||
// Fill the first row with max possible values we would get by just adding
|
||||
// or not adding the first item to the knapsack.
|
||||
for (let weightIndex = 1; weightIndex <= this.weightLimit; weightIndex += 1) {
|
||||
const itemIndex = 0;
|
||||
const itemWeight = this.possibleItems[itemIndex].weight;
|
||||
const itemValue = this.possibleItems[itemIndex].value;
|
||||
knapsackMatrix[itemIndex][weightIndex] = itemWeight <= weightIndex ? itemValue : 0;
|
||||
}
|
||||
|
||||
// Go through combinations of how we may add items to knapsack and
|
||||
// define what weight/value we would receive using Dynamic Programming
|
||||
// approach.
|
||||
for (let itemIndex = 1; itemIndex < this.possibleItems.length; itemIndex += 1) {
|
||||
for (let weightIndex = 1; weightIndex <= this.weightLimit; weightIndex += 1) {
|
||||
const currentItemWeight = this.possibleItems[itemIndex].weight;
|
||||
const currentItemValue = this.possibleItems[itemIndex].value;
|
||||
|
||||
if (currentItemWeight > weightIndex) {
|
||||
// In case if item's weight is bigger then currently allowed weight
|
||||
// then we can't add it to knapsack and the max possible value we can
|
||||
// gain at the moment is the max value we got for previous item.
|
||||
knapsackMatrix[itemIndex][weightIndex] = knapsackMatrix[itemIndex - 1][weightIndex];
|
||||
} else {
|
||||
// Else we need to consider the max value we can gain at this point by adding
|
||||
// current value or just by keeping the previous item for current weight.
|
||||
knapsackMatrix[itemIndex][weightIndex] = Math.max(
|
||||
currentItemValue + knapsackMatrix[itemIndex - 1][weightIndex - currentItemWeight],
|
||||
knapsackMatrix[itemIndex - 1][weightIndex],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now let's trace back the knapsack matrix to see what items we're going to add
|
||||
// to the knapsack.
|
||||
let itemIndex = this.possibleItems.length - 1;
|
||||
let weightIndex = this.weightLimit;
|
||||
|
||||
while (itemIndex > 0) {
|
||||
const currentItem = this.possibleItems[itemIndex];
|
||||
const prevItem = this.possibleItems[itemIndex - 1];
|
||||
|
||||
// Check if matrix value came from top (from previous item).
|
||||
// In this case this would mean that we need to include previous item
|
||||
// to the list of selected items.
|
||||
if (
|
||||
knapsackMatrix[itemIndex][weightIndex] &&
|
||||
knapsackMatrix[itemIndex][weightIndex] === knapsackMatrix[itemIndex - 1][weightIndex]
|
||||
) {
|
||||
// Check if there are several items with the same weight but with the different values.
|
||||
// We need to add highest item in the matrix that is possible to get the highest value.
|
||||
const prevSumValue = knapsackMatrix[itemIndex - 1][weightIndex];
|
||||
const prevPrevSumValue = knapsackMatrix[itemIndex - 2][weightIndex];
|
||||
if (
|
||||
!prevSumValue ||
|
||||
(prevSumValue && prevPrevSumValue !== prevSumValue)
|
||||
) {
|
||||
this.selectedItems.push(prevItem);
|
||||
}
|
||||
} else if (knapsackMatrix[itemIndex - 1][weightIndex - currentItem.weight]) {
|
||||
this.selectedItems.push(prevItem);
|
||||
weightIndex -= currentItem.weight;
|
||||
}
|
||||
|
||||
itemIndex -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
get totalValue() {
|
||||
/** @var {KnapsackItem} item */
|
||||
return this.selectedItems.reduce((accumulator, item) => {
|
||||
return accumulator + item.totalValue;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
get totalWeight() {
|
||||
/** @var {KnapsackItem} item */
|
||||
return this.selectedItems.reduce((accumulator, item) => {
|
||||
return accumulator + item.totalWeight;
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user