No-backtrack knapsack

You remember this:
                             capacity
                             0   1   2   3   4   5   6   7   8   9  10  11
                             ---------------------------------------------
Item   $   #         ∅      |0   0   0   0   0   0   0   0   0   0   0   0
  1    1   1        {1}     |0   1   1   1   1   1   1   1   1   1   1   1
  2    6   2       {1,2}    |0   1   6   7   7   7   7   7   7   7   7   7
  3   18   5      {1,2,3}   |0   1   6   7   7  18  19  24  25  25  25  25
  4   22   6     {1,2,3,4}  |0   1   6   7   7  18  22  24  28  29  29  40
  5   28   7    {1,2,3,4,5} |0   1   6   7   7  18  22  28  29  34  34  40
That’s a lot of data, O(N·W). We’d rather not keep it all around. Do we need it all? In fact, to compute row i of the table, we only need row i−1. We only need two rows at a time. Hooray! We’ve reduced the space required from O(N·W) to O(2W).

No, wait. We need all that data to backtrack, to reveal the solution. Or, …, do we? If we had a better @#$%& algorithm, then we might not have to do backtracking at all.

Let’s take another look at divide & conquer. Can we split the 11# knapsack in half, say, into 5# & 6# knapsacks? Not in general, no. What if there were an essential 7# item? Same for any other arbitrary division, e.g., 3# & 8#.

It looks like we can’t just split the knapsack in half and work out which items go into each half.

Instead of splitting the knapsack, let’s split the items. Let’s split the items arbitrarily into two groups, then figure out how much knapsack to allocate to each group. We’re not saying that we are going to steal every item in the group—it’s just that we’re considering them together. We might take some and not others.

Here’s our original table, putting (possibly) all items into a single 11# knapsack:

                             capacity
                             0   1   2   3   4   5   6   7   8   9  10  11
                             ---------------------------------------------
Item   $   #         ∅      |0   0   0   0   0   0   0   0   0   0   0   0
  1    1   1        {1}     |0   1   1   1   1   1   1   1   1   1   1   1
  2    6   2       {1,2}    |0   1   6   7   7   7   7   7   7   7   7   7
  3   18   5      {1,2,3}   |0   1   6   7   7  18  19  24  25  25  25  25
  4   22   6     {1,2,3,4}  |0   1   6   7   7  18  22  24  28  29  29  40
  5   28   7    {1,2,3,4,5} |0   1   6   7   7  18  22  28  29  34  34  40
Let’s put the first three items into one sub-knapsack of yet-undetermined size, and the second two into another sub-knapsack of the remaining size. What sizes should these be? Don’t ask me. Let’s work out all that we do know, for all sub-knapsack sizes 0–11#:
                             capacity
                             0   1   2   3   4   5   6   7   8   9  10  11
                             ---------------------------------------------
Item   $   #         ∅      |0   0   0   0   0   0   0   0   0   0   0   0
  1    1   1        {1}     |0   1   1   1   1   1   1   1   1   1   1   1
  2    6   2       {1,2}    |0   1   6   7   7   7   7   7   7   7   7   7
  3   18   5      {1,2,3}   |0   1   6   7   7  18  19  24  25  25  25  25

                             capacity
                             0   1   2   3   4   5   6   7   8   9  10  11
                             ---------------------------------------------
Item   $   #         ∅      |0   0   0   0   0   0   0   0   0   0   0   0
  4   22   6        {4}     |0   0   0   0   0   0  22  22  22  22  22  22
  5   28   7       {4,5}    |0   0   0   0   0   0  22  28  28  28  28  28
Great. How much space do we allocate to each group? How much space does the first group {1,2,3} get? Clearly, {4,5} will get the rest.

How many possible divisions of space are there? What profit do we make from each one?

Clearly, the best division of space is to give 5# to {1,2,3} and the remaining 6# to {4,5,6}. Let’s truncate our previous tables to show only those capacities:
                             capacity
                             0   1   2   3   4   5
                             ---------------------
Item   $   #         ∅      |0   0   0   0   0   0
  1    1   1        {1}     |0   1   1   1   1   1
  2    6   2       {1,2}    |0   1   6   7   7   7
  3   18   5      {1,2,3}   |0   1   6   7   7  18
                             capacity
                             0   1   2   3   4   5   6
                             -------------------------
Item   $   #         ∅      |0   0   0   0   0   0   0
  4   22   6        {4}     |0   0   0   0   0   0  22
  5   28   7       {4,5}    |0   0   0   0   0   0  22
Great! We have successfully divided our data into two halves, {1,2,3} and 4,5}. We still don’t know which items to steal, but our problems are now smaller.

What next? Recursion, of course. We take the {4,5} table, split it in half, creating two smaller tables:

                             capacity
                             0   1   2   3   4   5   6
                             -------------------------
Item   $   #         ∅      |0   0   0   0   0   0   0
  4   22   6        {4}     |0   0   0   0   0   0  22
                             capacity
                             0   1   2   3   4   5   6
                             -------------------------
Item   $   #         ∅      |0   0   0   0   0   0   0
  5   28   7        {5}     |0   0   0   0   0   0   0
Apply the same comparisons, and we decide to allocate 6# to {4}, and 0# to {5}. Therefore, we take item 4, and don’t take item 5. Remember that somewhere. Return, and deal with the {1,2,3} collection. Split it into two halves {1,2} and {3}. Recurse. Etc.

We split the data set in half each time, so we recurse at most log₂(N) deep, until the list of items becomes a singleton. Each table that we build has at most two rows at a time, and the tables become narrower as we recurse.