背包问题

来源:互联网 发布:java前端框架 编辑:程序博客网 时间:2024/06/07 07:08

I found the Knapsack problem tricky and interesting at the same time. I am sure if you are visiting this page, you already know the problem statement but just for the sake of completion :

Problem:

Given a Knapsack of a maximum capacity of W and N items each with its own value and weight, throw in items inside the Knapsack such that the final contents has the maximum value. Yikes !!!

 
 
 
Knapsack.svg

  • Link to the problem page in wiki

Here’s the general way the problem is explained – Consider a thief gets into a home to rob and he carries a knapsack. There are fixed number of items in the home – each with its own weight and value – Jewellery, with less weight and highest value vs tables, with less value but a lot heavy. To add fuel to the fire, the thief has an old knapsack which has limited capacity. Obviously, he can’t split the table into half or jewellery into 3/4ths. He either takes it or leaves it.

Example :

1Knapsack Max weight     :       W = 10 (units)
2 
3    Total items             :       N = 4
4 
5    Values of items         :       v[] = {10403050}
6 
7    Weight of items         :       w[] = {5463}

A cursory look at the example data tells us that the max value that we could accommodate with the limit of max weight of 10 is 50 + 40 = 90 with a weight of 7.

Approach:

The way this is optimally solved is using dynamic programming – solving for smaller sets of knapsack problems and then expanding them for the bigger problem.

Let’s build an Item x Weight array called V (Value array):

1V[N][W] = 4 rows * 10 columns

Each of the values in this matrix represent a smaller Knapsack problem.

Base case 1 : Let’s take the case of 0th column. It just means that the knapsack has 0 capacity. What can you hold in them? Nothing. So, let’s fill them up all with 0s.

Base case 2 : Let’s take the case of 0 row. It just means that there are no items in the house. What do you do hold in your knapsack if there are no items. Nothing again !!! All zeroes.

Varray0

Solution:

  1. Now, let’s start filling in the array row-wise. What does row 1 and column 1 mean? That given the first item (row), can you accommodate it in the knapsack with capacity 1 (column). Nope. The weight of the first item is 5. So, let’s fill in 0. In fact, we wouldn’t be able to fill in anything until we reach the column 5 (weight 5).
  2. Once we reach column 5 (which represents weight 5) on the first row, it means that we could accommodate item 1. Let’s fill in 10 there (remember, this is a Value array):

     
    Varray1

  3. Moving on, for weight 6 (column 6), can we accommodate anything else with the remaining weight of 1 (weight – weight of this item => 6 – 5). Hey, remember, we are on the first item. So, it is kind of intuitive that the rest of the row will just be the same value too since we are unable to add in any other item for that extra weight that we have got.

     
    Varray2

  4. So, the next interesting thing happens when we reach the column 4 in third row. The current running weight is 4.

We should check for the following cases.

  1. Can we accommodate Item 2 – Yes, we can. Item 2’s weight is 4.
  2. Is the value for the current weight is higher without Item 2? – Check the previous row for the same weight. Nope. the previous row* has 0 in it, since we were not able able accommodate Item 1 in weight 4.
  3. Can we accommodate two items in the same weight so that we could maximize the value? – Nope. The remaining weight after deducting the Item2’s weight is 0.

Varray3

Why previous row?

Simply because the previous row at weight 4 itself is a smaller knapsack solution which gives the max value that could be accumulated for that weight until that point (traversing through the items).

Exemplifying,

  1. The value of the current item = 40
  2. The weight of the current item = 4
  3. The weight that is left over = 4 – 4 = 0
  4. Check the row above (the Item above in case of Item 1 or the cumulative Max value in case of the rest of the rows). For the remaining weight 0, are we able to accommodate Item 1? Simply put, is there any value at all in the row above for the given weight?

The calculation goes like so :

  1. Take the max value for the same weight without this item:
    1previous row, same weight = 0
    2 
    3=> V[item-1][weight]
  2. Take the value of the current item + value that we could accommodate with the remaining weight:
    1Value of current item
    2+ value in previous row with weight 4(total weight until now (4) - weight of the current item (4))
    3 
    4=> val[item-1] + V[item-1][weight-wt[item-1]]

    Max among the two is 40 (0 and 40).

  3. The next and the most important event happens at column 9 and row 2. Meaning we have a weight of 9 and we have two items. Looking at the example data we could accommodate the first two items. Here, we consider few things:
    11. The value of the current item = 40
    22. The weight of the current item = 4
    33. The weight that is left over = 9 4 5
    44. Check the row above.  At the remaining weight 5, are we able to accommodate Item 1.

     
    Varray4

So, the calculation is :

  1. Take the max value for the same weight without this item:
    1previous row, same weight = 10
  2. Take the value of the current item + value that we could accumulate with the remaining weight:
    1Value of current item (40)
    2+ value in previous row with weight 5(total weight until now (9) - weight of the current item (4))
    3 
    4=10

    10 vs 50 = 50.

At the end of solving all these smaller problems, we just need to return the value at V[N][W] – Item 4 at Weight 10:

Varray5

Complexity

Analyzing the complexity of the solution is pretty straight-forward. We just have a loop for W within a loop of N => O (NW)

Implementation:

Here comes the obligatory implementation code in Java:

01classKnapsack {
02 
03    publicstatic void main(String[] args) throwsException {
04        intval[] = {10,40,30,50};
05        intwt[] = {5,4,6,3};
06        intW = 10;
07 
08        System.out.println(knapsack(val, wt, W));
09    }
10 
11    publicstatic int knapsack(intval[], intwt[], intW) {
12 
13        //Get the total number of items.
14        //Could be wt.length or val.length. Doesn't matter
15        intN = wt.length;
16 
17        //Create a matrix.
18        //Items are in rows and weight at in columns +1 on each side
19        int[][] V = newint[N + 1][W + 1];
20 
21 
22        //What if the knapsack's capacity is 0 - Set
23        //all columns at row 0 to be 0
24        for(intcol = 0; col <= W; col++) {
25            V[0][col] = 0;
26        }
27 
28        //What if there are no items at home. 
29        //Fill the first row with 0
30        for(introw = 0; row <= N; row++) {
31            V[row][0] = 0;
32        }
33 
34        for(intitem=1;item<=N;item++){
35 
36            //Let's fill the values row by row
37            for(intweight=1;weight<=W;weight++){
38 
39                //Is the current items weight less
40                //than or equal to running weight
41                if(wt[item-1]<=weight){
42 
43//Given a weight, check if the value of the current
44//item + value of the item that we could afford
45//with the remaining weight is greater than the value
46//without the current item itself
47                    V[item][weight]=Math.max (val[item-1]+V[item-1][weight-wt[item-1]], V[item-1][weight]);
48                }
49                else{
50//If the current item's weight is more than the
51//running weight, just carry forward the value
52//without the current item
53                    V[item][weight]=V[item-1][weight];
54                }
55            }
56 
57        }
58 
59        //Printing the matrix
60        for(int[] rows : V) {
61            for(intcol : rows) {
62 
63                System.out.format("%5d", col);
64            }
65            System.out.println();
66        }
67 
68        returnV[N][W];
69 
70    }
71 
72}



 上一篇博客从概念上说了一下贪心算法,这次我们通过一个实例,来进一步帮助大家理解贪心算法。

 

    一、【经典实例:】(背包问题)

    给定n个物品和一个容量为C的背包,物品i的重量是Wi,其价值为Vi,背包问题是如何选择入背包的物品,使得装入背包的物品的总价值最大,注意和0/1背包的区别,在背包问题中可以将物品的一部分装入背包,但不能重复装入。

 

    二、【想法:】

    用贪心法求解背包问题的关键是如何选定贪心策略,使得按照一定的顺序选择每个物品,并尽可能的装入背包,知道背包装满。至少有三种看似合适的贪心策略。

  1. 选择价值最大的物品,因为这可以尽可能快的增加背包的总价值,但是,虽然每一步选择获得了背包价值的极大增长,但背包容量却可能消耗的太快,使得装入背包的物品个数减少,从而不能保证目标函数达到最大。
  2. 选择重量最轻的物品,因为这可以装入尽可能多的物品,从而增加背包的总价值。但是,虽然每一步选择使背包的容量消耗的慢了,但背包的价值却没能保证迅速的增长,从而不能保证目标函数达到最大。
  3. 以上两种贪心策略或者只考虑背包价值的增长,或者只考虑背包容量的消耗,而为了求得背包问题的最优解,需要在背包价值增长和背包容量消耗二者之间寻找平衡。正确的贪心策略是选择单位重量价值最大的物品。

    例如:有三个物品,其重量分别为{20,30,10},价值分别为{60,120,50},背包的容量为50,应用三种贪心策略装入背包的物品和获得的价值如下图所示:

        

     三、【算法:】

    设背包容量为C,共有n个物品,物品重量存放在数组W[n]中,价值存放在数组V[n]中,问题的解存放在数组X[n]中,贪心法求解背包问题的算法如下:



算法:贪心法求解背包问题

输入:背包的容量C,物品重量W[n],物品价值V[n]

输出:数组X[n]

  1. 改变数组WV的排列顺序,使其按单位重量价值V[i]/W[i]降序排列;
  2. 将数组X[n]初始化为0
  3. i=0
  4. 循环直到(W[i]>C

    4.1   将第i个物品放入背包:X[i]=1;

    4.2    C=C-W[i];

    4.3    i++;

  1. X[i]=C/W[i]

 


     四、【算法分析:】

    算法的时间主要消耗在将各种物品按照单位重量的价值从大到小的排序,因此,其时间复杂性为O(nlog2n)

    背包问题与0/1背包问题类似,所不同的是在选择物品i(1)装入背包时。可以选择一部分,而不一定要全部装入背包。背包问题可以用贪心法求解,而0/1背包问题却不能用贪心法求解,下图给出了一个贪心法求解0/1背包问题的示例。从下图可以看出,对于0/1背包问题,贪心法之所以不能得到最优解,是由于物品不允许分割,因此,无法保证最终能将背包装满,部分闲置的背包容量使背包的单位重量价值降低了。事实上,在考虑0/1背包问题时,应比较选择该物品和不选择该物品所导致的方案,然后再做出最优选择,由此导出许多相互重叠的子问题,所以,0/1背包问题合适用动态规划法求解。


        

     五、【算法实现:】

    实现函数KnapSacks实现贪心法求解背包问题,简单起见,假设物品已按单位重量降序排列,算法C++语言描述如下:

[cpp] view plaincopyprint?
  1. int KnapSack (int W[],int V[],int N,int C)  
  2. {  
  3.     double X[10]={0};  
  4.     int maxValue=0;  
  5.     for(int i=0;W[i]<C;i++)  
  6.     {  
  7.         X[i]=1;  
  8.         maxValue+=V[i];  
  9.         C=C-W[i];  
  10.           
  11.     X[i]=(double)C/W[i];  
  12.     maxValue+=X[i]*V[i];  
  13.     return maxValue;  
  14.     }  
  15. }  


     六、【总结:】

    贪心法并不是从整体最优考虑,它所做出的选择只是在某种意义上的局部最优。贪心算法对于大部分的优化问题都能产生最优解,但不能总获得整体最优解,通常可以获得近似最优解。




0 0