如何使用背包算法找出袋子中有哪些元素,而不仅仅是袋子的价值?

19

我这里有一段代码,使用背包算法(二进制装箱NP难问题)计算出最优值:

int Knapsack::knapsack(std::vector<Item>& items, int W)
{
    size_t n = items.size();
    std::vector<std::vector<int> > dp(W + 1, std::vector<int>(n + 1, 0));
    for (size_t j = 1; j <= n; j++)
    {
        for ( int w = 1; w <= W; w++)
        {
            if (items[j-1].getWeight() <= w)
            {
                dp[w][j] = std::max(dp[w][j-1], dp[w - items[j-1].getWeight()][j-1] + items[j-1].getWeight());
            }
            else
            {
                dp[w][j] = dp[w][j - 1];
            }
        }
    }
    return dp[W][n];
}

我还需要显示打包中包含的元素。我想创建一个数组来存储所选元素。因此问题是,在哪个步骤可以执行此选择?是否有其他更有效的方法来确定已经选取了哪些项目?

我希望能够知道给我最优解的项目,而不仅仅是最佳解决方案的价值。


2
有点难理解你的问题,但我猜你想知道哪些项目可以给你最优解,而不仅仅是最佳解的价值? - Rob Neuhaus
4个回答

14

可以使用矩阵中的数据而不需要存储任何额外数据来获取打包在矩阵中的元素。

伪代码:

line <- W
i <- n
while (i > 0):
  if dp[line][i] - dp[line - weight(i)][i-1] == value(i):
      // the element 'i' is in the knapsack
      i <- i-1 // only in 0-1 knapsack
      line <- line - weight(i)
  else: 
      i <- i-1 

其背后的思想是遍历矩阵;如果重量差恰好等于元素大小,则该元素在背包中。否则,该物品不在背包中,请继续进行其他操作。


这是非常好的伪代码。但是使用它,我只能得到添加元素的权重,而我还需要它们的名称。我正在考虑做相同的事情,但将数组“dp”更改为“Item”类型。你对此有什么看法? - prvit
@nightcrime:使用这个算法,你可以确切地知道袋子里有哪些元素,你可以在开始运行算法之前创建一个容器[我们称之为bag],并在运行算法时:如果dp[line][i] - dp[line][i-1] == value(i),那么bag.add(items[i-1]),其中items是输入到背包函数的物品向量。在算法结束时,bag将包含所有在袋子里的元素,而且只有它们。 - amit
我明白了。但是它只在我仅添加了一个元素的情况下才有效。否则,语句dp[line][i] - dp[line][i-1] == value(i)从未成立。 - prvit
哦,我使用了 items[i] 而不是 items[i-1]。非常愚蠢的错误 :) 谢谢,你真的救了我 :) - prvit
3
你还可以简单地检查 dp[line][i] 是否等于 dp[line][i-1]。如果是这样,那么就选择第 i 个物品。 - Rushil Paul
显示剩余3条评论

2

对于有界0/1背包中取物品的算法比这个帖子中现有代码所表现出来的要简单。本答案旨在将该过程解密一下,并提供一个干净、直接的实现以及一个已经工作过的例子。


方法

从两个指标开始,分别是与表轴相对应的weight变量和一个沿着DP查找表的项目轴向后循环的索引i,停止于索引1(算法使用i-1,因此停止于1可以避免越界访问)。

在循环中,如果T[weight][i] != T[weight][i-1],则标记项目i-1为选定状态,扣除其重量并继续沿着项目轴向后移动。

重构的时间复杂度为O(length(items))

以下是Python伪代码:

def reconstruct_taken_items(T, items, capacity):
    taken = []
    weight = capacity

    for i in range(len(items), 0, -1): # from n downto 1 (inclusive)
        if T[weight][i] != T[weight][i-1]:
            taken.append(items[i-1])
            weight -= items[i-1].weight

   return taken

例子

比如,考虑一个背包容量为9,有以下物品:

[item(weight=1, value=2), 
 item(weight=3, value=5), 
 item(weight=4, value=8), 
 item(weight=6, value=4)]

通过选择物品0、1和2,最佳价值为15。

DP查找表如下:

items ---->

0  1  2  3  4
--------------+
0  0  0  0  0 | 0  capacity
0  2  2  2  2 | 1     |
0  2  2  2  2 | 2     |
0  2  5  5  5 | 3     v
0  2  7  8  8 | 4
0  2  7 10 10 | 5
0  2  7 10 10 | 6
0  2  7 13 13 | 7
0  2  7 15 15 | 8
0  2  7 15 15 | 9

在此运行重构算法:
0  0  0  0  0
0  2  2  2  2
0  2  2  2  2
0  2  5  5  5
0  2  7  8  8
0  2  7 10 10
0  2  7 10 10
0  2  7 13 13
0  2  7 15 15
0  2  7 15 15 <-- weight = capacity = 9
        ^   ^
        |   |
      i-1   i = length(items) = 4

在上面的初始状态中,T[weight][i] == T[weight][i-1] (15 == 15),因此未选择item[i-1]item(weight=6, value=4))。将i减一并尝试剩余重量相同的物品。
0  0  0  0  0
0  2  2  2  2
0  2  2  2  2
0  2  5  5  5
0  2  7  8  8
0  2  7 10 10
0  2  7 10 10
0  2  7 13 13
0  2  7 15 15
0  2  7 15 15 <-- weight = 9
        ^
        |
        i = 3

这里,T[weight][i] != T[weight][i-1] (7 != 15),所以必须选择items[i-1],即item(weight=4, value=8)。将剩余的重量减去items[i-1].weight,即9 - 4 = 5,然后尝试在剩下的重量范围内寻找更小的物品。请注意保留HTML标签。
0  0  0  0  0
0  2  2  2  2
0  2  2  2  2
0  2  5  5  5
0  2  7  8  8
0  2  7 10 10 <-- weight = 5
0  2  7 10 10
0  2  7 13 13
0  2  7 15 15
0  2  7 15 15
      ^
      |
      i = 2

在这种情况下,我们再次有T[weight][i] != T[weight][i-1]2 != 7),因此我们必须拿走items[i-1],即items[1]item(weight=3, value=5)。将剩余重量减去items[i-1].weight,即5 - 3,然后移到下一个物品。
0  0  0  0  0
0  2  2  2  2
0  2  2  2  2 <-- weight = 2
0  2  5  5  5
0  2  7  8  8
0  2  7 10 10
0  2  7 10 10
0  2  7 13 13
0  2  7 15 15
0  2  7 15 15
   ^
   |
   i = 1

在这最后一步中,我们再次有 T[weight][i] != T[weight][i-1] (0 != 2),因此我们必须选择 items[i-1],即 items[0] 或者 item(weight=1, value=2)。将剩余的重量减去 items[i-1].weight,或者 2 - 1,并退出循环,因为 i == 0

C++实现

#include <iostream>
#include <vector>

class Knapsack {
public:
    struct Item {
        const int weight;
        const int value;
    };

private:
    static std::vector<Item> reconstruct_taken_items(
        const std::vector<std::vector<int> > &T,
        const std::vector<Item> &items,
        const int capacity
    ) {
        std::vector<Item> taken;
        int weight = capacity;
    
        for (size_t i = items.size(); i > 0; i--) {
            if (T[weight][i] != T[weight][i-1]) {
                taken.emplace_back(items[i-1]);
                weight -= items[i-1].weight;
            }
        }
    
        return taken;
    }

public:
    static std::vector<Item> solve(
        const std::vector<Item> &items, 
        const int capacity
    ) {
        std::vector<std::vector<int> > T(
            capacity + 1,
            std::vector<int>(items.size() + 1, 0)
        );
        
        for (int i = 1; i <= capacity; i++) {
            for (size_t j = 1; j <= items.size(); j++) {
                const Item &item = items[j-1];

                if (item.weight > i) {
                    T[i][j] = T[i][j-1];
                }
                else {
                    T[i][j] = std::max(
                        T[i-item.weight][j-1] + item.value, 
                        T[i][j-1]
                    );
                }
            }
        }
        
        return reconstruct_taken_items(T, items, capacity);
    }
};

int main() {
    const int capacity = 9;
    const std::vector<Knapsack::Item> items = {
        {1, 2}, {3, 5}, {4, 8}, {6, 4}
    };

    for (const Knapsack::Item &item : Knapsack::solve(items, capacity)) {
        std::cout << "weight: " << item.weight 
                  << ", value: " << item.value << "\n";
    }

    return 0;
}

参考资料


2
line <- W
i <- n
while (i> 0):
  if dp[line][i] - dp[line - weight(i) ][i-1] == value(i):
    the element 'i' is in the knapsack
    cw = cw - weight(i)
    i <- i-1
  else if dp[line][i] > dp[line][i-1]:
    line <- line - 1
  else: 
    i <- i-1

只需要记住当您添加第i个项目时,如何到达dp[line][i]

dp[line][i] = dp[line - weight(i) ][i - 1] + value(i);

0
这是一个 Julia 实现:
function knapsack!{F<:Real}(
    selected::BitVector,    # whether the item is selected
    v::AbstractVector{F},   # vector of item values (bigger is better)
    w::AbstractVector{Int}, # vector of item weights (bigger is worse)
    W::Int,                 # knapsack capacity (W ≤ ∑w)
    )

    # Solves the 0-1 Knapsack Problem
    # https://en.wikipedia.org/wiki/Knapsack_problem
    # Returns the assigment vector such that
    #  the max weight ≤ W is obtained

    fill!(selected, false)

    if W ≤ 0
        return selected
    end

    n = length(w)
    @assert(n == length(v))
    @assert(all(w .> 0))

    ###########################################
    # allocate DP memory

    m = Array(F, n+1, W+1)
    for j in 0:W
        m[1, j+1] = 0.0
    end

    ###########################################
    # solve knapsack with DP

    for i in 1:n
        for j in 0:W
            if w[i] ≤ j
                m[i+1, j+1] = max(m[i, j+1], m[i, j-w[i]+1] + v[i])
            else
                m[i+1, j+1] = m[i, j+1]
            end
        end
    end

    ###########################################
    # recover the value

    line = W
    for i in n : -1 : 1
        if line - w[i] + 1 > 0 && m[i+1,line+1] - m[i, line - w[i] + 1] == v[i]
            selected[i] = true
            line -= w[i]
        end
    end

    selected
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接