例如,使用列表拉链,可以遍历一维空间。是否有任何类似的优雅和有效的方法来编码通过二维网格行走(没有模式)的概念?
答案 0 :(得分:6)
真正的问题是,你想通过2D网格做什么?
是随机访问还是某种模式?动态编程问题经常发生 建模为遍历2D网格,但它不是随机访问,它是相当的 图案。我们可以使用的模式。
例如,考虑在两个字符串之间找到编辑距离的问题,我们给出了:
-- the cost of replacing one character with another
charEditCost :: Char -> Char -> Int
-- the cost of inserting a character
charInsertCost :: Char -> Int
我们可以给出以下重复次数来确定两个字符串之间的编辑距离:
editDistance [] [] = 0
editDistance (a:as) [] = editDistance as [] + charInsertCost a
editDistance [] (b:bs) = editDistance [] bs + charInsertCost b
editDistance (a:as) (b:bs) = minimum
[ editDistance as bs + charEditCost a b
, editDistance (a:as) bs + charInsertCost b
, editDistance as (b:bs) + charInsertCost a
]
但那真的很低效 - 请注意第四个等式中editDistance as bs
将如何计算三次 - 一次直接,
一次按editDistance (a:as) bs
,一次按editDistance as (b:bs)
。
动态编程技术告诉我们引入二维网格来缓存结果:
editDistance as bs = last . last $ grid where
firstRow j = grid !! 0 !! (j-1) + charInsertCost (as!!j)
firstCol i = grid !! (i-1) !! 0 + charInsertCost (bs!!i)
innerCel i j = minimum
[ grid !! (i-1) !! (j-1) + charEditCost (as!!j) (bs!!i)
, grid !! i !! (j-1) + charInsertCost (as!!j)
, grid !! (i-1) !! j + charInsertCost (bs!!j)
]
grid = ( 0 : [ firstRow j | j <- [1..length as] ] ) :
[ ( firstCol i : [ innerCel i j | j <- [1..length as] ] ) | i <- [1..length bs ]
由于!!
是O(n),这仍然会产生可怕的渐近线。但我们可以通过注意到我们不需要随机访问来改进这一点;我们确切地知道我们需要计算哪些单元格来计算网格的每个单元格。所以我们需要做的就是在需要时提供这些细胞。
非常像经典fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
提供fibs!!(i-1)
和fibs!!(i-2)
及时计算fibs!!i
,
我们也可以在这里做同样的事。
editDistance as bs = last . last $ grid where
firstRow = scanl (+) 0 $ map charInsertCost as
firstCol = scanl (+) 0 $ map charInsertCost bs
grid = ( 0 : firstRow ) : zipWith3 mkRow bs firstCol grid
mkRow b firstCel lastRow = let thisRow = firstCel : zipWith4 (mkCel b) as thisRow lastRow (tail lastRow) in thisRow
mkCel b a leftCel aboveLeftCel aboveCel = minimum
[ aboveLeftCel + charEditCost b a
, aboveCel + charInsertCost b
, leftCel + charInsertCost a
]
并非2D网格上的每个问题都适合这种打结,但它确实适用于某些人。