我首先提供了创建和处理惰性二维矩阵的函数。
mkList f = map f [0..]
mkTable f = mkList (\i -> mkList (\j -> f i j))
tableIndex table i j = table !! i !! j
然后我为给定的背包问题制作一个特定的表格
knapsackTable = mkTable f
where f 0 _ = 0
f _ 0 = 0
f i j | ws!!i > j = leaveI
| otherwise = max takeI leaveI
where takeI = tableIndex knapsackTable (i-1) (j-(ws!!i)) + vs!!i
leaveI = tableIndex knapsackTable (i-1) j
-- weight value pairs; item i has weight ws!!i and value vs!!i
ws = [0,1,2, 5, 6, 7] -- weights
vs = [0,1,7,11,21,31] -- values
最后,添加一些辅助函数来查看表格。
viewTable table maxI maxJ = take (maxI+1) . map (take (maxJ+1)) $ table
printTable table maxI maxJ = mapM_ print $ viewTable table maxI maxJ
这部分还是比较容易的。但我想更进一步。
我希望表格有一个更好的数据结构。理想情况下,它应该是:
- 未封装(不可变)[编辑]不要紧
- 延迟
- 无界
O(1)
时间复杂度来构建- 查找给定条目的时间复杂度为
O(1)
,
(更现实的是,最坏情况下O(log n)
,其中n是查找第i行、第j列的条目时i*j
)
此外,如果你能进一步概括
knapsackTable
,并证明它的效率,那么将获得额外的奖励积分。在改进数据结构时,你应该尝试满足以下目标:
如果我要求解的最大重量为10(在我的当前代码中,这将是
indexTable knapsackTable 5 10
,其中5表示包括第1-5个物品),则应该只执行必要的最小工作量。理想情况下,这意味着不需要对每行表格的脊柱强制执行O(i*j)
的工作以达到必要的列长度。如果您认为DP意味着评估整个表格,则可以说这不是“真正”的DP。如果我要求打印整个表格(类似于
printTable knapsackTable 5 10
),则每个条目的值应计算一次且仅计算一次。给定单元格的值应取决于其他单元格的值(DP风格:即永远不要重新计算相同的子问题)。
想法:
- Data.Array 有界 :(
- UArray 严格 :(
- 备忘录技术 (有关Haskell中DP的SO问题) 这可能有效
对我的陈述理念做出一些妥协的答案将会得到我的赞同(无论如何),只要它们是有信息性的。最少妥协的答案可能会被“接受”。
mkTable f = mkList (mkList . f)
更易读。 - yairchu