6

I have a variation on the Knapsack Problem that I'm struggling to find an efficient solution for.

Let's say you have multiple groups of items. Each group can have an arbitrary number of items, each with a value and weight. The problem is to find the set of items with maximum total value, weight < some limit, and (the tricky part) only sets than include one item from EVERY group are valid.

That is, imagine you have hundreds of items to pick from, but you must take one sandwich, one beverage, one snack, one flashlight, etc. Not just that you can't take more than one from any group, but you must at the end of the day end up with exactly g total items if there are g groups.

It seems like this should be faster to do than the basic problem, because so many combinations are invalid, but I'm struggling to find a solution.

Dov Rosenberg
  • 673
  • 6
  • 20
  • Your problem seems flawed. What if the requirements of selecting exactly `g` items and weight limit conflict? – Lingxi Apr 19 '15 at 12:12
  • An easy (yet exponential in the number of "groups") solution is to add boolean dimension for each group that indicates if an item was chosen or not from this set. – amit Apr 19 '15 at 12:24
  • @Lingxi - excellent point. Let's assume we know (as we could easily confirm ahead of time) that at least one solution exists. – Dov Rosenberg Apr 19 '15 at 16:46
  • @amit - So simply skip over items that are in already used groups? Wouldn't that risk both creating a suboptimal answer and creating an answer where not every group was represented? – Dov Rosenberg Apr 19 '15 at 17:15

2 Answers2

4

Example code in C++. The function returns the maximum achievable value, or -1 if no feasible solutions exist. It runs in O(n * max_weight) where n is the total number of items counting all groups, and max_weight is the weight limit. The complexity is essential the same as the classic algorithm that solves the original knapsack problem. The code implements the algorithm in Evgeny Kluev's answer.

int CalcMaxValue(const std::vector<std::vector<int>>& weight,
                 const std::vector<std::vector<int>>& value,
                 int max_weight) {
    std::vector<int> last(max_weight + 1, -1);
    if (weight.empty()) return 0;
    for (int i = 0; i < weight[0].size(); ++i) {
        if (weight[0][i] > max_weight) continue;
        last[weight[0][i]] = std::max(last[weight[0][i]], value[0][i]);
    }
    std::vector<int> current(max_weight + 1);
    for (int i = 1; i < weight.size(); ++i) {
        std::fill(current.begin(), current.end(), -1);
        for (int j = 0; j < weight[i].size(); ++j) {
            for (int k = weight[i][j]; k <= max_weight; ++k) {
                if (last[k - weight[i][j]] < 0) continue;
                current[k] = std::max(current[k], last[k - weight[i][j]] + value[i][j]);
            }
        }
        std::swap(current, last);
    }    
    return *std::max_element(last.begin(), last.end());
}
Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • 2
    Thanks for the example. Any ideas on how to alter this to return multiple optimal solutions? – ahf90 Aug 25 '15 at 21:49
3

For integer weights and not too large limit you could apply usual dynamic programming approach (slightly modified).

Use a pair of arrays that map every possible weight to value. One of these arrays (A) holds the result for those groups that are already processed. Other array (B) is used to receive sum of values from the first array and from items of the group being currently processed. When coming from one group to another, swap these arrays and clear array B. At the end (as usual) you have to get the largest value from array B.

Asymptotic complexities are the same as for usual dynamic programming algorithm. But your conclusion that this should be faster to do than the basic problem is somewhat true because you could process each element of the same group independently from each other, so this modification of usual algorithm is better parallelizable.

Evgeny Kluev
  • 24,287
  • 7
  • 55
  • 98
  • I'm not sure I understand. Using the typical approach where m[i,w] is the maximum achievable value with weight less than w by using the first i items... is the proposed A[n,w] the max value of m[i,w] for all i in group n? Or does A still contain all the individual results? – Dov Rosenberg Apr 19 '15 at 17:07
  • 1
    I propose to change meaning of m[i,j] to the following: maximum achievable value with weight less than j by using the first i **groups** (not items). Then odd rows of `m` are stored in array `A`, even rows - in `B` because it's enough to store only last 2 rows to get the result (though if you need simple way to get all items used in the solution, it's better to keep whole matrix). And I assume sorting items by group, then using all items in the group to produce next row. – Evgeny Kluev Apr 19 '15 at 17:15
  • Thanks, Evgeny and @Lingxi but doesn't that risk failing to include an item from one or more groups? Could that be solved by finding the minimum weight required for all the "future" groups and adjusting the max_weight to take that into account in each iteration? Wouldn't that risk a suboptimal solution though? – Dov Rosenberg Apr 19 '15 at 18:29
  • @DovRosenberg The algorithm selects exactly one item from each group. And if no feasible solutions exist, it returns `-1` instead. – Lingxi Apr 19 '15 at 18:36
  • @DovRosenberg: hard to say without seeing the actual algorithm, but most likely attempts to solve this by adjusting the max_weight would give suboptimal solutions. – Evgeny Kluev Apr 19 '15 at 18:50