哈斯克尔背包

Posted

技术标签:

【中文标题】哈斯克尔背包【英文标题】:Haskell Knapsack 【发布时间】:2012-02-12 12:12:52 【问题描述】:

我已经用 Scala 中的每个项目之一写了一个有界背包问题的答案,并尝试将其转换为 Haskell,结果如下:

knapsack :: [ ( Int, Int ) ] -> [ ( Int, Int ) ] -> Int -> [ ( Int, Int ) ]
knapsack xs [] _   = xs
knapsack xs ys max =
    foldr (maxOf) [ ] [ knapsack ( y : xs ) ( filter (y /=) ys ) max | y <- ys
        , weightOf( y : xs ) <= max ]

maxOf :: [ ( Int, Int ) ] -> [ ( Int, Int ) ] -> [ ( Int, Int ) ]
maxOf a b = if valueOf a > valueOf b then a else b

valueOf :: [ ( Int, Int ) ] -> Int
valueOf [ ]        = 0
valueOf ( x : xs ) = fst x + valueOf xs

weightOf :: [ ( Int, Int ) ] -> Int
weightOf [ ]        = 0
weightOf ( x : xs ) = snd x + weightOf xs

我不是在寻找关于如何清理代码的提示,只是为了让它工作。据我所知,它应该执行以下操作:

对于每个元组选项(在 ys 中) 如果当前元组 (y) 和运行总和 (xs) 组合的权重小于容量 使用可用的元组(以 ys 为单位)减去当前元组,得到包含当前元组和当前总数 (xs) 的最佳背包 最后,获取这些结果中最有价值的并返回

*编辑:* 抱歉,忘了说哪里出了问题……所以它编译得很好,但它给出了错误的答案。对于以下输入,我期望什么以及它产生什么:

knapsack [] [(1,1),(2,2)] 5
Expect: [(1,1),(2,2)]
Produces: [(1,1),(2,2)]

knapsack [] [(1,1),(2,2),(3,3)] 5
Expect: [(2,2),(3,3)]
Produces: []

knapsack [] [(2,1),(3,2),(4,3),(6,4)] 5
Expect: [(2,1),(6,4)]
Produces: []

所以我想知道造成这种差异的原因是什么?

解决方案,感谢 sepp2k:

ks = knapsack []

knapsack :: [ ( Int, Int ) ] -> [ ( Int, Int ) ] -> Int -> [ ( Int, Int ) ]
knapsack xs [] _   = xs
knapsack xs ys max =
    foldr (maxOf) [ ] ( xs : [ knapsack ( y : xs ) ( ys #- y ) max
                             | y <- ys, weightOf( y : xs ) <= max ] )

(#-) :: [ ( Int, Int ) ] -> ( Int, Int ) -> [ ( Int, Int ) ]
[ ]        #- _ = [ ]
( x : xs ) #- y = if x == y then xs else x : ( xs #- y )

maxOf :: [ ( Int, Int ) ] -> [ ( Int, Int ) ] -> [ ( Int, Int ) ]
maxOf a b = if valueOf a > valueOf b then a else b

valueOf :: [ ( Int, Int ) ] -> Int
valueOf [ ]        = 0
valueOf ( x : xs ) = fst x + valueOf xs

weightOf :: [ ( Int, Int ) ] -> Int
weightOf [ ]        = 0
weightOf ( x : xs ) = snd x + weightOf xs

返回预期结果,如上。

【问题讨论】:

有什么问题?它不编译吗?它会给出错误的结果吗?具体一点。 【参考方案1】:

ys 包含时,您的第一个案例会触发。所以对于knapsack [foo,bar] [] 42,你会得到[foo, bar],这就是你想要的。然而,当ys 不包含任何内容时,它不会触发,除了会使您超过最大权重的元素,即knapsack [(x, 20), (y,20)] [(bla, 5)] 将返回[] 并因此丢弃先前的结果。由于这不是您想要的,您应该调整您的案例,以便仅当 ys 中至少有一个低于最大权重的元素时才会触发第二种情况。

做到这一点的一种方法是在递归时丢弃任何使您超过最大权重的元素,这样就不会发生这种情况。

另一种方法是切换案例的顺序,并在第一个案例中添加一个保护,说明 ys 必须包含至少一个不会让您超过总重量的元素(并将另一个案例调整为不需要ys 为空)。

PS:另一个与您的代码无关的问题是它忽略了重复项。 IE。如果您在列表 [(2,2), (2,2)] 上使用它,它会表现得好像列表只是 [(2,2)],因为 filter (y /=) ys 会丢弃所有出现的 y,而不仅仅是一个。

【讨论】:

第一种情况没有触发的原因是因为我将重量太大的元素的处理委托给第二种情况,如果它们的重量加上运行总数使它们超过最大限度。我正在对你所说的进行解释(对不起,我没有完全理解,所以它可能看起来与你的意思完全不同......) @eZanmoto 是的,但这就是问题所在。第二种情况会丢弃它们,如果在丢弃它们之后ys 是空的,你会得到 [] 结果,而实际上你想得到xs 抱歉,我现在明白了,它可以工作了,非常感谢 :) 我正在使用正确的解决方案再次更新我的结果。【参考方案2】:

对您的工作版本的一些改进:

import Data.List
import Data.Function(on)

ks = knapsack []

knapsack :: [(Int, Int)] -> [(Int, Int)] -> Int -> [(Int, Int)]
knapsack xs [] _   = xs
knapsack xs ys max =
    foldr (maxOf) [] (xs: [knapsack (y:xs) (delete y ys) max
                           | y <- ys, weightOf(y:xs) <= max ] ) where
                             weightOf = sum . map snd

maxOf :: [(Int, Int)] -> [(Int, Int)] -> [(Int, Int)]
maxOf a b = maximumBy (compare `on` valueOf) [a,b] where
            valueOf = sum . map fst

【讨论】:

【参考方案3】:

我可以建议使用动态编程方法吗?这种解决 0-1 背包问题的方法几乎慢得令人痛苦,至少当变量的数量大于 20 左右时是这样。虽然它很简单,但效率太低了。这是我的镜头:

import Array

-- creates the dynamic programming table as an array
dynProgTable (var,cap) = a where
    a = array ((0,0),(length var,cap)) [ ((i,j), best i j)
                       | i <- [0..length var] , j <- [0..cap] ] where
        best 0 _ = 0
        best _ 0 = 0
        best i j
            | snd (var !! (i-1)) > j = a!decline
            | otherwise          = maximum [a!decline,value+a!accept]
                where decline = (i-1,j)
                      accept  = (i-1,j - snd (var !! (i-1)))
                      value   = fst (var !! (i-1))

--Backtracks the solution from the dynamic programming table
--Output on the form [Int] where i'th element equals 1 if
--i'th variable was accepted, 0 otherwise.
solve (var,cap) =
    let j = cap
        i = length var
        table = dynProgTable (var,cap)
        step _ 0 _ = []
        step a k 0 = step table (k-1) 0 ++ [0]
        step a k l
            | a!(k,l) == a!(k-1,l) = step a (k-1) l ++ [0]
            | otherwise            = step a (k-1) (l - snd (var !! (k-1))) ++ [1]
    in step table i j

在输入(var,cap)中,var是2元组(c,w)形式的变量列表,其中c是成本,w是权重。 cap 是最大重量限额。

我确信上面的代码可以被清理以使其更具可读性和明显性,但这就是我的结果:) 上面 Landei 的代码 sn-p 很短,我的计算机需要很长时间来计算实例只有 20 个变量。上面的动态规划方法给了我一个更快的 1000 个变量的解决方案。

如果你不了解动态规划,你应该看看这个链接:Lecture slides on dynamic programming,它对我帮助很大。

有关数组的介绍,请查看Array tutorial。

【讨论】:

以上是关于哈斯克尔背包的主要内容,如果未能解决你的问题,请参考以下文章

哪种最大选择算法更快? (哈斯克尔)[重复]

使用scanl时空间泄漏在哪里? (哈斯克尔)

`ghc-pkg` 和 `cabal` 程序有啥关系? (哈斯克尔)

哈斯克尔。我很困惑这个代码片段是如何工作的

哈斯克尔中类型的函数应用操作数($)?

背包整理(01背包,完全背包,多重背包,分组背包)(待更新)