如何使用背包算法 [而不仅仅是包的价值] 找到包中的哪些元素?
Posted
技术标签:
【中文标题】如何使用背包算法 [而不仅仅是包的价值] 找到包中的哪些元素?【英文标题】:How to find which elements are in the bag, using Knapsack Algorithm [and not only the bag's value]? 【发布时间】:2011-11-21 07:25:38 【问题描述】:这里我有使用背包算法计算最优值的代码(装箱 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];
我还需要显示包中包含的元素。我想创建一个数组来放置所选元素。所以问题是,我可以在哪一步执行这个选择?有没有其他更有效的方法来确定哪些物品已被拿走?
我希望能够知道给我最佳解决方案的项目,而不仅仅是最佳解决方案的价值。
【问题讨论】:
有点难理解你的问题,但我猜你想知道给你最佳解决方案的项目,而不仅仅是最佳解决方案的价值? 【参考方案1】:可以使用矩阵中的数据来获取您从矩阵中打包的元素,而无需存储任何额外的数据。
伪代码:
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
类型。你的观点是什么?
@nightcrime: 使用这个算法,你可以确切地知道包里有哪个元素,你可以在开始这个算法之前创建一个容器[让我们称之为bag
,在运行算法时:如果dp[line][i] - dp[line][i-1] == value(i)
然后是 bag.add(items[i-1])
,其中 items
是背包函数的项目输入向量。在算法结束时,bag
将包含包中的所有元素,并且只包含它们。
:我知道了。但它仅在我仅添加 1 个元素时才有效。在其他方面,声明 dp[line][i] - dp[line][i-1] == value(i) 永远不会是真的。(
@nightcrime:我不确定我是否在关注你,背包算法,我的回答也是如此,不允许你将项目“i”添加到包中两次 [或 3/ 4次]。如果添加元素 i、j、k:此算法将找到所有元素,因为 dp[line][i]-dp[line][i-1] == value(i)
、dp[line][j]-dp[line][j-1] == value(j)
和 dp[line][k]-dp[line][k-1] == value(k)
。
你也可以简单地检查dp[line][i] != dp[line][i-1]
。如果这是真的,那么第 i 个项目被拿走。【参考方案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);
【讨论】:
【参考方案3】:用于重建有界 0/1 背包中的物品的算法比该线程中的一些现有代码更简单,可能会让人相信。这个答案旨在稍微揭开这个过程的神秘面纱,并提供一个干净、直接的实现以及一个工作示例。
方法
从对应于表轴的两个索引开始:一个weight
变量初始化为背包容量,一个索引i
沿着项目轴在DP 查找表上向后循环,在索引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]
,即items[2]
,或item(weight=4, value=8)
,一定是被占用了。将剩余重量减少items[i-1].weight
或9 - 4 = 5
,并尝试将item[i-1]
移出图片后剩余重量较小的剩余物品。
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;
另见
Printing Items that are in sack in knapsack Knapsack 0-1 path reconstruction (which items to take)【讨论】:
【参考方案4】:这是一个 julia 实现:
function knapsack!F<:Real(
selected::BitVector, # whether the item is selected
v::AbstractVectorF, # vector of item values (bigger is better)
w::AbstractVectorInt, # 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
【讨论】:
以上是关于如何使用背包算法 [而不仅仅是包的价值] 找到包中的哪些元素?的主要内容,如果未能解决你的问题,请参考以下文章