对于任意数量的(散列)数组,我们可以使用动态规划有效地解决这个“背包问题”。求解时间大致与数组数量和平均数组大小的平方成正比。
我的第一个想法是编写代码,但后来决定这将花费太长时间,并且可能不是解释算法如何工作的最佳方式。相反,我决定展示如何为问题中给出的示例计算最佳解决方案。对于任意数量的数组,如何实现该算法应该是显而易见的。
[编辑:毕竟我决定编写这个代码。我在末尾添加了 tidE]
首先考虑(“最后一个”)数组c。这是 Excel 电子表格中相关信息的屏幕截图。
请注意,我没有在偏移量0、{ cost: 10, value: 21} 处包含哈希值。那是因为该哈希被偏移量2、{ cost: 10, value: 22} 处的那个“支配”。前者永远不会被首选,因为它们的成本相同,而后者的价值更高。
第二个表显示了对于给定的“剩余预算”(即,在选择了 a 和 b 中的哈希之后),哪个哈希是首选的。请注意,可能的剩余预算范围从3(数组c 中的最低成本)到24(总预算,30 减去在a 和@987654336 中选择哈希的成本)成本最低的@:30 - 2 - 4 = 24)。
最好用哈希数组来实现:
[{budget: 3, value: 10, hash_offset: 3 },
{budget: 4, value: 10, hash_offset: 3 },
...
{budget: 8, value: 10, hash_offset: 3 },
{budget: 9, value: 20, hash_offset: 1 },
{budget: 10, value: 22, hash_offset: 2 },
...
{budget: 10, value: 22, hash_offset: 2 }]
现在让我们继续讨论数组b。
这里我只包含数组b 中包含的四个哈希中的两个,因为偏移量1、{ cost: 9, value: 20 } 的哈希由偏移量0、{ cost: 4, value: 20 } 的哈希和哈希控制{ cost: 12, value: 10} 被 { cost: 4, value: 20} 支配。
这里的“可用预算”范围从7 到27。 7 提供了在b 中选择一个哈希(在偏移量0,成本为4)和在c 中选择一个哈希值(在偏移量3,成本为3)的最低要求。范围的上限 27 是选择 a 中的哈希后可能剩余的最大值 (30 - 3)。
假设剩余预算(数组b 和c)为18。如果我们要在偏移量0 处选择散列,我们将从该散列中实现20 的值,并且有18 - 4 => 14 的剩余预算用于在数组c 中选择散列。从数组c 的表中,我们看到我们将选择数组c 中偏移量2 处的散列,其值为22。因此,如果可用预算为 18 并且我们在数组 b 中选择偏移量 0 处的哈希,我们将获得数组 b 和 c 的(最大值)20 + 22 => 42。
我们现在必须考虑选择数组b 中偏移量2 处的哈希值,这将产生22 的值,并留下18 - 15 => 3 的剩余预算用于选择数组c 中的哈希值。我们从数组c 的表中看到,这将是偏移量3 处的哈希值10。因此,如果可用预算为18,并且我们在数组b 中选择了偏移量2 处的哈希,我们将有22 + 10 => 32 的数组b 和c 的(最大值)值。
因此,如果我们要为数组b 和c 提供18 的可用预算,则最佳选择是在数组b 中的偏移量0 和偏移量@ 处选择散列数组c 中的987654393@,总(最大值)值为42。我已在表格中概述了该选择。
同样的结果适用于18 - 23 范围内的任何剩余预算。将对每个其他可用预算范围执行类似的计算。当可用预算为24 时,您会看到平局。
我们现在可以转到数组a。 (我们快完成了。)
我没有包含偏移量 0 处的哈希,因为它由偏移量 2 处的哈希支配。
数组a、b 和c 的可用预算是30,因此我们不必以此为条件(即对于第一个数组)。我们必须考虑三种可能性:
选择偏移量1 处的散列以获得20 的值,从而为数组b 和c 留下剩余预算30 - 9 => 21,其中(来自数组@ 的表987654410@) 为最后两个数组生成最佳值42,总值为20 + 42 => 62。
选择偏移量2 处的哈希值以获取22 的值,这使得数组b 和c 的剩余预算为30 - 10 => 20,其中(来自数组@ 的表987654418@) 为最后两个数组生成最佳值 42,总值为 22 + 42 => 64。
选择偏移量3 处的哈希以获得10 的值,从而为数组b 和c 留下剩余预算30 - 2 => 28,其中(来自数组@ 的表987654426@) 为最后两个数组生成最佳值44,总值为10 + 44 => 54。
因此,我们得出结论,通过选择数组a 中偏移量2 处的哈希、数组b 中偏移量0 处的哈希和偏移量@987654434 处的哈希来实现64 的最大值数组中的@c。
代码
def knapsack(all_costs_and_values, total_budget)
costs_and_values = remove_dominated_choices(all_costs_and_values)
budget = remaining_budget(costs_and_values, total_budget)
solution = optimize(costs_and_values, budget)
display_solution(costs_and_values, solution)
end
.
private
def remove_dominated_choices(a)
a.map do |f|
# f.invert.invert ensures that no two keys have the same value
g = f.invert.invert
g.each_with_object({}) do |(name,v),h|
(h[name] = v) unless g.any? { |_,p|
(p[:cost] <= v[:cost] && p[:value] > v[:value]) ||
(p[:cost] < v[:cost] && p[:value] >= v[:value]) }
end
end
end
def remaining_budget(b, tot_budget)
mc = min_cost_per_hash(b)
b.map.with_index do |h,i|
if i.zero?
(tot_budget..tot_budget)
else
(mc[i..-1].reduce(:+)..tot_budget - mc[0..i-1].reduce(:+))
end
end
end
def min_cost_per_hash(arr)
arr.map { |h| h.values.min_by { |h| h[:cost] }[:cost] }
end
.
def optimize(costs_and_values,remaining_budget)
solution = Array.new(costs_and_values.size)
i = costs_and_values.size-1
g = costs_and_values[i].sort_by { |k,v| -v[:cost] }
solution[i] = remaining_budget[i].each_with_object({}) do |rb,h|
name, f = g.find { |_,v| v[:cost] <= rb }
h[rb] = { name: name, value_onward: f[:value] }
end
while i > 0
i -= 1
g = costs_and_values[i].sort_by { |k,v| v[:cost] }
min_to_leave = remaining_budget[i+1].first
solution[i] = remaining_budget[i].each_with_object({}) do |rb,h|
best = - Float::INFINITY
g.each do |name, v|
leave_for_next = rb - v[:cost]
break if leave_for_next < min_to_leave
candidate = v[:value] + solution[i+1][leave_for_next][:value_onward]
if candidate > best
best = candidate
h[rb] = { name: name, value_onward: candidate }
end
end
end
end
solution
end
。
def display_solution(costs_and_values, solution)
rv = solution.first.keys.first
puts "Optimal value: #{ solution.first[rv][:value_onward] }\n"
solution.each_with_index do |h,i|
name = h[rv][:name]
puts " Best choice for hash #{i}: #{name}"
rv -= costs_and_values[i][name][:cost]
end
end
示例
我将all_costs_and_values 的数据结构更改为哈希数组,其中键是成本/值对的标签(例如,'12' 指的是以前在行偏移量1 处的哈希值,列偏移量2 在 OP 的数组中)。我希望标签被替换为更有意义的字符串。
all_costs_and_values =
[{ '00' => { cost: 10, value: 20 }, '01' => { cost: 8, value: 17 },
'02' => { cost: 10, value: 20 }, '03' => { cost: 12, value: 24 } },
{ '10' => { cost: 6, value: 16 }, '11' => { cost: 4, value: 14},
'12' => { cost: 6, value: 17 }, '13' => { cost: 5, value: 13 } },
{ '20' => { cost: 8, value: 16 }, '21' => { cost: 14, value: 30 },
'22' => { cost: 16, value: 32 }, '23' => { cost: 14, value: 32 } },
{ '30' => { cost: 2, value: 4 }, '31' => { cost: 5, value: 9 },
'32' => { cost: 10, value: 16 }, '33' => { cost: 6, value: 8 } }]
total_budget = 30
knapsack(all_costs_and_values, total_budget)
#=> Optimal value: 70
# Best choice for hash 0: 01
# Best choice for hash 1: 12
# Best choice for hash 2: 23
# Best choice for hash 3: 30
在计算最优值时,构造散列数组solution:
solution
#=> [{ 30=>{:name=>"01", :value_onward=>70}},
# "01" => { cost: 8, value: 17 } leave 30-8 = 22
#=> {14-15=>{:name=>"11", :value_onward=>34},
#=> 16=>{:name=>"12", :value_onward=>37},
#=> 17-18=>{:name=>"11", :value_onward=>39},
#=> 19=>{:name=>"12", :value_onward=>42},
#=> 20-21=>{:name=>"11", :value_onward=>50},
#=> 22=>{:name=>"12", :value_onward=>53}},
# "12" => { cost: 6, value: 17 } leave 22-6 = 16
#=> {10-12=>{:name=>"20", :value_onward=>20},
#=> 13-15=>{:name=>"20", :value_onward=>25},
#=> 16-18=>{:name=>"23", :value_onward=>36},
# "23" => { cost: 14, value: 32 } leave 16-14 = 2
#=> {2-4=>{:name=>"30", :value_onward=>4},
# "30" => { cost: 2, value: 4 } leave 2-2 = 0
#=> 5-9=>{:name=>"31", :value_onward=>9},
#=> 10=>{:name=>"32", :value_onward=>16}}]