【问题标题】:Function complexity from exponential to linear函数复杂度从指数到线性
【发布时间】:2017-05-27 04:12:54
【问题描述】:

我有以下具有指数复杂度的函数:

c :: Integer -> Integer
c n 
   | n <= 4 = n + 10
   | otherwise = c (n-1) + 2 * (c (n-4))

我正在努力使这个函数的复杂性变为线性。

c x 应该终止,即使 1000

【问题讨论】:

标签: haskell time-complexity


【解决方案1】:

至少有两种方法可以及时线性地解决这个问题。

使用中间内存

c1 :: Integer -> Integer
c1 n 
  | n <= 4 = n + 10
  | otherwise = go n (map c1 [4,3,2,1])
  where
    go 4 (a:_) = a 
    go n [a,b,c,d] = go (n-1) [a + 2*d, a, b, c]

这里我们使用四个中间寄存器并在going 循环时将它们移位。我们可以使用元组(a, b, c, d) 来代替列表,但是从这里开始映射会更方便。

这个解在空间上是恒定的,在时间上是线性的。

记忆(codata 生成)

c2 :: Integer -> Integer
c2 n
  | n <= 4 = n + 10
  | otherwise = results !! fromInteger (n - 1)
  where
    results = [11..14] ++ zipWith f (drop 3 results) results 
    f a b = a + 2*b

这里我们使用 Haskell 的惰性(普通评估策略 + memoization)。无限列表results 按需生成值。它被c2 函数用作数据,它只是向生成器请求n-th 数字,并且是自定义的。同时,这些数据在需要时才存在。这种类型的数据称为 codata,在 Haskell 中很常见。

这个解在空间和时间上是线性的。

两种解决方案都处理负数和大正数。

【讨论】:

  • 另一种方法是用矩阵乘法来表示递归。也就是说,给定序列的四个连续元素的列向量,您可以在左侧将它们乘以固定矩阵,以获得序列的下四个元素。 m * m * m * ... * v。但这只是m ^ k * v,并且使用平方取幂(stimesMonoid),这种计算实际上比c1 更有效。
  • @dfeuer,这确实是用于循环公式的优秀数学方法,既优雅又实用。在一般非数字(或非线性)情况下,迭代循环或记忆仍然有用。
猜你喜欢
  • 2017-08-04
  • 1970-01-01
  • 1970-01-01
  • 2014-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-09
相关资源
最近更新 更多