I have found this code to solve Knapsack Problem using brute force mechanism (this is mostly for learning, so no need to point out dynamic is more efficient). I got the code to work, and understand most of it. MOST. Here's the question:
I've noticed those two conditionals, that I have no idea how they work and why they are in the code - I know they are vital, since any changes I've made caused the algorithm to produce wrong results:
// if bit not included then skip
if (((i >> j) & 1) != 1) continue;
// if bit match then add
if (((bestPosition >> j) & 1) == 1)
{
include.Add(Items[j]);
}
Here's the whole class, and the way I'm calling it out from main:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace KnapSack2
{
class BruteForce
{
public double Capacity { get; set; }
public Item[] Items { get; set; }
public Data Run()
{
int bestValue = 0;
int bestPosition = 0;
int size = Items.Length;
var permutations = (long)Math.Pow(2,size);
for (int i = 0; i<permutations; i++)
{
int total = 0;
int weight = 0;
for (int j = 0; j<size; j++)
{
//jeżeli bit "not included" to omin"
if(((i>>j)&1)!=1)
continue;
total += Items[j].v;
weight += Items[j].w;
}
if (weight <= Capacity && total>bestValue)
{
bestPosition = i;
bestValue = total;
}
}
var include = new List<Item>();
for (int j = 0; j<size; j++)
{
// jeżeli bit pasuje, wtedy dodaj
if (((bestPosition>>j) & 1)==1)
include.Add(Items[j]);
}
return new Data { BestValue = bestValue, Include = include };
}//End of Run
}
}
Calling out in main
var ks = new BruteForce
{
Capacity = MaxWeight,
Items = items.ToArray()
};
Data result = ks.Run();
Item class is just a simple object holding value, weight and ID
This
&
is thebitwise-AND
While this
>>
is the right-shift operatorThat being said, let's take the following expression
and try to understand it.
Initially, this
i >> j
will shift right the bits ofi
byj
positions.For instance, let we have the following assignment:
The binary representation of
number
is:If we define a new integer as:
Then binary representation of
shiftNumbersBitByOne
would be:Then on the result of this operation and 1, we apply the bitwise AND operator.
Despite the fact that the definition is clear, an example will make it more clear.
Let that we have the binary numbers
a
andb
, then the result ofa&b
is the following:That being said, in this operation
(i >> j) & 1
we apply the bitwise-AND operator between the result ofi >> j
and the binary representation of 1This will happen if and only if the last digit of
i >> j
is 1.Update
Above we addressed the how part -I have no idea how they work. Now we will address the why part -why they are in the code.
Let's define our problem, the Knapsack problem. According to wikipedia
According to the above, it is straightforward that
and
Furthermore, based on your code, we can deduce that each item has a value and a weight that can be accessed as
item.v
anditem.w
respectively. I suggest you rename this to value and weight respectively, in order your code to be more meaningful.Apparently, this
int size = Items.Length;
is the number of the available items.The whole point of why part starts here:
What is the
permutations
? What doespermutations
represent?Before we answer this question, let's think about how we can represent which items of the items collection will be used in the final solution. I argue that we can represent this with a n-bit number provided that we have n items. How is that possible? If each bit in the n-bit number refers to one of the n-items, it is pretty evident we can do so. The value of the n-th bit would be 0, if the n-th item will not be included in the final solution. While it's value would be 1, if it will be included.
That being said is pretty clear what permutations represents. It represents all the possible combinations of the items in the final solution. This is clear, because each bit can have 2 values, either 0 or 1. Given that we have n-bits, the number of the possible combinations is 2^n.
Actually, for this reason this algorithm is a brute force algorithm (we do an exhaustive search). We visit all the possible combinations to find the best one. In the following loop:
you loop through all the possible combinations.
Then foreach combination, you loop through the items collection:
Now if the
weight
is less than the limit value and the total value is greater than the best current total value, we pick this combination as the current best:At the end, having looped through all the available combinations, we will have the best one.
Having found the best combination, we have to loop through the items to see which of them are included in this combination.
Apparently the code parts in question are checks for a certain bit being set, as indicated by the comments. The condition
is true if and only if the
j
-th bit ofi
is zero; the conditionis true if and only if the
j
-th bit ofbestPosition
is one. Concerning the bigger picture, apparently the implementation usesint
to model a set of items, where thej
-th bit is set if and only if thej
-th item is included in the set; consequently, membership tests can be done by bit checks. The implementation enumerates all subsets of the items (usingint
to represent them) to perform exhaustive search.Note that the Delphi implementation for sets uses the same approach, but hides the bit indexing from client code.