背包算法的变体

Posted

技术标签:

【中文标题】背包算法的变体【英文标题】:A variant of the Knapsack algorithm 【发布时间】:2018-02-01 10:35:21 【问题描述】:

我有一个项目列表a, b, c,...,每个项目都有一个权重和一个值。

“普通”背包算法将找到最大化所选项目价值的项目选择,同时确保重量低于给定约束。

我遇到的问题略有不同。我希望最小化该值(通过使用该值的倒数很容易),同时确保权重至少是给定约束的值,小于或等于约束。

我已经尝试通过普通的背包算法重新路由这个想法,但这无法做到。我希望有另一种我不知道的组合算法可以做到这一点。

【问题讨论】:

如果你运行普通背包,将约束设置为所有物品的总重量减去给定的约束,你没有选择的物品集合不就是你想要的集合吗? 【参考方案1】:

在德语 wiki 中,它的形式化为:

finite set of objects U
w: weight-function
v: value-function

w: U -> R
v: U -> R
B in R    # constraint rhs

Find subset K in U subject to:
    sum( w(u) <= B ) | all w in K
such that: 
    max sum( v(u) )  | all u in K

所以没有像非否定性这样的限制。

只需使用负权重、负值和负 B。 基本概念是:

 sum( w(u) ) <=  B | all w in K
<->
-sum( w(u) ) >= -B | all w in K

所以在你的情况下:

classic constraint: x0 + x1 <=  B    | 3 + 7 <= 12 Y | 3 + 10 <= 12 N
becomes:           -x0 - x1 <= -B    |-3 - 7 <=-12 N |-3 - 10 <=-12 Y 

因此,对于给定的实现,是否允许这样做取决于软件。就优化问题而言,没有问题。您的案例的整数规划公式与经典公式一样自然(且有界)。

基于整数编程的 Python Demo

代码

import numpy as np
import scipy.sparse as sp
from cylp.cy import CyClpSimplex
np.random.seed(1)

""" INSTANCE """
weight = np.random.randint(50, size = 5)
value = np.random.randint(50, size = 5)
capacity = 50

""" SOLVE """
n = weight.shape[0]
model = CyClpSimplex()
x = model.addVariable('x', n, isInt=True)
model.objective = value                            # MODIFICATION: default = minimize!
model += sp.eye(n) * x >= np.zeros(n)              # could be improved
model += sp.eye(n) * x <= np.ones(n)               # """
model += np.matrix(-weight) * x <= -capacity       # MODIFICATION
cbcModel = model.getCbcModel()
cbcModel.logLevel = True
status = cbcModel.solve()
x_sol = np.array(cbcModel.primalVariableSolution['x'].round()).astype(int)  # assumes existence

print("INSTANCE")
print("    weights: ", weight)
print("    values: ", value)
print("    capacity: ", capacity)
print("Solution")
print(x_sol)
print("sum weight: ", x_sol.dot(weight))
print("value: ", x_sol.dot(value))

小备注

此代码只是一个使用类似库的低级演示,还有其他可用的工具可能更适合(例如 windows:pulp) 这是来自wiki 的经典整数规划公式,如上所述修改 它可以很好地扩展,因为底层求解器非常好 正如所写,它正在解决 0-1 背包问题(仅需要更改变量边界)

小看核心代码:

# create model
model = CyClpSimplex()

# create one variable for each how-often-do-i-pick-this-item decision
# variable needs to be integer (or binary for 0-1 knapsack)
x = model.addVariable('x', n, isInt=True)

# the objective value of our IP: a linear-function
# cylp only needs the coefficients of this function: c0*x0 + c1*x1 + c2*x2...
#     we only need our value vector
model.objective = value                            # MODIFICATION: default = minimize!

# WARNING: typically one should always use variable-bounds
#     (cylp problems...)
#  workaround: express bounds lower_bound <= var <= upper_bound as two constraints
#  a constraint is an affine-expression
#  sp.eye creates a sparse-diagonal with 1's
#  example: sp.eye(3) * x >= 5
#           1 0 0 -> 1 * x0 + 0 * x1 + 0 * x2 >= 5
#           0 1 0 -> 0 * x0 + 1 * x1 + 0 * x2 >= 5
#           0 0 1 -> 0 * x0 + 0 * x1 + 1 * x2 >= 5
model += sp.eye(n) * x >= np.zeros(n)              # could be improved
model += sp.eye(n) * x <= np.ones(n)               # """

# cylp somewhat outdated: need numpy's matrix class
# apart from that it's just the weight-constraint as defined at wiki
# same affine-expression as above (but only a row-vector-like matrix)
model += np.matrix(-weight) * x <= -capacity       # MODIFICATION

# internal conversion of type neeeded to treat it as IP (or else it would be 
LP)
cbcModel = model.getCbcModel()
cbcModel.logLevel = True
status = cbcModel.solve()

# type-casting
x_sol = np.array(cbcModel.primalVariableSolution['x'].round()).astype(int)  

输出

Welcome to the CBC MILP Solver 
Version: 2.9.9 
Build Date: Jan 15 2018 

command line - ICbcModel -solve -quit (default strategy 1)
Continuous objective value is 4.88372 - 0.00 seconds
Cgl0004I processed model has 1 rows, 4 columns (4 integer (4 of which binary)) and 4 elements
Cutoff increment increased from 1e-05 to 0.9999
Cbc0038I Initial state - 0 integers unsatisfied sum - 0
Cbc0038I Solution found of 5
Cbc0038I Before mini branch and bound, 4 integers at bound fixed and 0 continuous
Cbc0038I Mini branch and bound did not improve solution (0.00 seconds)
Cbc0038I After 0.00 seconds - Feasibility pump exiting with objective of 5 - took 0.00 seconds
Cbc0012I Integer solution of 5 found by feasibility pump after 0 iterations and 0 nodes (0.00 seconds)
Cbc0001I Search completed - best objective 5, took 0 iterations and 0 nodes (0.00 seconds)
Cbc0035I Maximum depth 0, 0 variables fixed on reduced cost
Cuts at root node changed objective from 5 to 5
Probing was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Gomory was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Knapsack was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Clique was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
MixedIntegerRounding2 was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
FlowCover was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
TwoMirCuts was tried 0 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)

Result - Optimal solution found

Objective value:                5.00000000
Enumerated nodes:               0
Total iterations:               0
Time (CPU seconds):             0.00
Time (Wallclock seconds):       0.00

Total time (CPU seconds):       0.00   (Wallclock seconds):       0.00

INSTANCE
    weights:  [37 43 12  8  9]
    values:  [11  5 15  0 16]
    capacity:  50
Solution
[0 1 0 1 0]
sum weight:  51
value:  5

【讨论】:

萨莎,非常感谢您的回复。阅读了您的 Wiki 解释后,现在这很有意义,但是,我不会假装我理解您的代码实现。你能给我简要介绍一下代码在做什么吗?再次感谢您的回复,非常有帮助 @user3745220 我添加了一些描述。这是一个微不足道的整数程序,只有库一开始看起来很可疑。

以上是关于背包算法的变体的主要内容,如果未能解决你的问题,请参考以下文章

我如何解决 0-1 背包算法的这些变体?

背包问题变体的递归关系?

背包算法变量

背包问题变体 - 将重量和价值最大化到极限

试图解决背包问题的变化

蓝桥杯之算法模板题 Python版