说我有以下功能:
minc = map (+1)
natural = 1:minc natural
它似乎像这样展开:
1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc...
1:2:minc(minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc...
1:2:minc(2:minc(2:minc(2:minc(2:minc(2:minc(2:minc(2:minc(2:minc(minc...
1:2:3:minc(3:minc(3:minc(3:minc(3:minc(3:minc(3:minc(3:minc(minc(minc...
...
虽然进行了懒惰评估,但要在列表中构建每个新号码n
,必须展开n
次表达式,这会使我们O(N^2)
复杂化。但是通过执行时间,我可以看到真正的复杂性仍然是线性的!
Haskell在这种情况下使用哪种优化以及它如何展开这个表达式?
答案 0 :(得分:5)
每个递归步骤之间共享自然列表。图表的评估方式如下。
1:map (+1) _
^ |
`---------'
1: (2 : map (+1) _)
^ |
`----------'
1: (2 : (3 : map (+1) _)
^ |
`----------'
此共享意味着代码使用O(n)时间而不是预期的O(N ^ 2)。
答案 1 :(得分:2)
要在列表中构建每个新数字
n
,必须展开n
次表达式,这样我们才能 O ( N 2 )复杂性。
不完全。以这种方式展开第一个 N 数字的复杂性实际上是 O ( N 2 ) 显然我错了 [1] 。但是,如果您只请 N 号码,那么它的实际评估结果如下:
(!!n) $ 1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc...
(!!n-1) $ minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc...
(!!n-1) $ (1+1):minc(minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc...
-- note that `(1+1)` isn't actually calculated!
(!!n-2) $ minc(minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc...
(!!n-2) $ ((1+1)+1):minc(minc(minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc...
-- again, neither of the additions is actually calculated.
(!!n-3) $ minc(minc(minc(1:minc(1:minc(1:minc(1:minc(1:minc(1:minc...
(!!n-3) $ ((...)+1):minc(minc(minc(minc(1:minc(1:minc(1:minc(1:minc(1:minc...
...
(!!n-n) $ ((...+1)+1) : minc(minc(...minc(minc(1:minc(...
╰─ n ─╯
(!!0) $ (n+1) : _
n+1
N 每增加一个固定数量的两个步骤,加上 N 一旦达到索引,它们仍会增加 - 这仍然是总而言之, O ( N )。
这里至关重要的是,map
基本上只对整个列表应用一次。它完全是懒惰的,即产生_:_
thunk它只需要知道列表至少 length 1,但实际的元素并不重要所有
这样,我们minc(minc(...(minc(1 : ...
所写的内容仅在一步中被(... + 1) : minc(...
取代。
[1] 事实证明,即使我们总和第一个 N 数字,它也在< EM> 0 的(名词的)。我不知道怎么做。