【问题标题】:NP-complete knapsackNP完全背包
【发布时间】:2011-09-09 08:35:55
【问题描述】:

我看到thisECLiPSe解决了thisXKCD漫画中提到的问题。我试图将其转换为纯 Prolog。

go:-
    Total = 1505,
    Prices = [215, 275, 335, 355, 420, 580],
    length(Prices, N),
    length(Amounts, N),
    totalCost(Prices, Amounts, 0, Total),
    writeln(Total).

totalCost([], [], TotalSoFar, TotalSoFar).
totalCost([P|Prices], [A|Amounts], TotalSoFar, EndTotal):-
    between(0, 10, A),
    Cost is P*A,
    TotalSoFar1 is TotalSoFar + Cost,
    totalCost(Prices, Amounts, TotalSoFar1, EndTotal).

我认为这不是人们能想到的最好/最具声明性的解决方案。有没有人有任何改进的建议?提前致谢!

【问题讨论】:

  • 从最昂贵的项目向后工作似乎很有效,因为这些允许从预算/背包中减去更小的数量倍数(更少的分支)。对现有代码的简单修改是检查 TotalSoFar 是否小于或等于 EndTotal。通过纸笔尝试,我很快找到了解决方案。

标签: prolog np-complete clpfd


【解决方案1】:

既然你提到了 SWI-Prolog 为什么不提到

?- use_module(library(clpfd)).

library(lambda)

?- Total = 1505, Prices = [215, 275, 335, 355, 420, 580],
   maplist(\P^A^M^(P*A #= M, A #>=0),Prices,Amounts,Ms), sum(Ms, #=, Total).

通过说明这一点,列表Amounts 中的所有变量都在有限范围内。所以没有必要为上限“做数学”(这通常会出错)。 要查看具体解决方案,需要 labeling/2:

?- Total = 1505, Prices = [215, 275, 335, 355, 420, 580],
   maplist(\P^A^M^(P*A #= M, A #>=0),Prices,Amounts,Ms), sum(Ms, #=, Total),
   labeling([], Amounts).
    Total = 1505,
    Prices = [215,275,335,355,420,580],
    Amounts = [1,0,0,2,0,1],
    Ms = [215,0,0,710,0,580] ;
    Total = 1505,
    Prices = [215,275,335,355,420,580],
    Amounts = [7,0,0,0,0,0],
    Ms = [1505,0,0,0,0,0].

【讨论】:

    【解决方案2】:

    任何具有几天以上经验的 Prolog 程序员都应该能够理解您的生成和测试方法。以下是一些小的调整:

    go(Amounts) :-
        Prices = [580, 420, 355, 335, 275, 215],
        totalCost(Prices, Amounts, 0, 1505),
        write(Amounts), nl.
    
    totalCost([], [], Total, Total).
    totalCost([P|Prices], [A|Amounts], SoFar, Total):-
        Upper is (Total-SoFar)//P,
        between(0,Upper,A),
        SoNear is SoFar + P*A,
        totalCost(Prices, Amounts, SoNear, Total).
    

    我将 go/0 更改为 go/1 以便 Prolog 引擎将回溯并生成所有解决方案(有两个)。可以省略对 length/2 的调用,因为 totalCost/4 负责构建列表 Amounts 的工作,使其与 Price 长度相等。我使用了 write/1nl/0 让它更便携。

    totalCost/4 中,我缩短了一些变量/参数的名称,并为累加器参数取了一个有点搞笑的名称。我强加检查我们的累加器不超过所需 Total 的方式使用您对 between/3 的原始调用,但使用计算的上限而不是常数。在我的机器上,它将运行时间从几分钟缩短到几秒钟。

    补充:我应该在这里提到我在上面的评论中所说的,菜单项现在是从最贵到最便宜的顺序排列的。使用 SWI-Prolog 的 time/1 谓词表明,这将工作量从 1,923 次推理减少到 1,070 次推理。主要的改进(速度)来自于对 A 使用计算的边界,而不是对每个项目使用 0 到 10 的范围。

    time((go(A),false)).
    

    注意复合目标周围的额外括号,否则 SWI-Prolog 认为我们正在调用未定义的 time/2 谓词。

    【讨论】:

      猜你喜欢
      • 2011-04-23
      • 1970-01-01
      • 1970-01-01
      • 2017-09-19
      • 1970-01-01
      • 2015-07-09
      • 1970-01-01
      • 2022-07-21
      • 1970-01-01
      相关资源
      最近更新 更多