小编典典

惰性评估和时间复杂度

algorithm

我一直在研究非临时性惰性评估,这使我想到了KeeganMcAllister的演讲:为什么要学习Haskell。在幻灯片8中,他显示了最小功能,定义为:

minimum = head . sort

并指出其复杂度为O(n)。我不明白,如果按替换排序为O(nlogn),为什么说复杂度是线性的。帖子中提到的排序不能是线性的,因为它不假设任何有关数据的信息,这是线性排序方法(例如计数排序)所要求的。

懒惰的评估在这里扮演着神秘的角色吗?如果是这样,其背后的解释是什么?


阅读 215

收藏
2020-07-28

共1个答案

小编典典

minimum = head . sort中,sort将不能完全做到,因为它不会做 前期
。将sort根据需要生产出的第一个元素,通过要求才会被尽可能多的完成head

在例如mergesort中,首先n将成对比较列表中的数字,然后将获胜者进行配对和比较(n/2数字),然后将新的获胜者(n/4)等。总而言之,O(n)进行比较以产生最小的元素。

mergesortBy less [] = []
mergesortBy less xs = head $ until (null.tail) pairs [[x] | x <- xs]
  where
    pairs (x:y:t) = merge x y : pairs t
    pairs xs      = xs
    merge (x:xs) (y:ys) | less y x  = y : merge (x:xs) ys
                        | otherwise = x : merge  xs (y:ys)
    merge  xs     []                = xs
    merge  []     ys                = ys

可以扩展上面的代码,以使用产生的大量比较标记它产生的每个数字:

mgsort xs = go $ map ((,) 0) xs  where
  go [] = []
  go xs = head $ until (null.tail) pairs [[x] | x <- xs]   where
    ....
    merge ((a,b):xs) ((c,d):ys) 
            | (d < b)   = (a+c+1,d) : merge ((a+1,b):xs) ys    -- cumulative
            | otherwise = (a+c+1,b) : merge  xs ((c+1,d):ys)   --   cost
    ....

g n = concat [[a,b] | (a,b) <- zip [1,3..n] [n,n-2..1]]   -- a little scrambler

运行它几个列表长度,我们看到 它确实是~ n

*Main> map (fst . head . mgsort . g) [10, 20, 40, 80, 160, 1600]
[9,19,39,79,159,1599]

要查看排序代码本身是否为~ n log n,我们对其进行更改,以使每个产生的数字都带有其自身的成本,然后通过对整个排序列表求和得出总成本:

    merge ((a,b):xs) ((c,d):ys) 
            | (d < b)   = (c+1,d) : merge ((a+1,b):xs) ys      -- individual
            | otherwise = (a+1,b) : merge  xs ((c+1,d):ys)     --   cost

这是各种长度列表的结果,

*Main> let xs = map (sum . map fst . mgsort . g) [20, 40, 80, 160, 320, 640]
[138,342,810,1866,4218,9402]

*Main> map (logBase 2) $ zipWith (/) (tail xs) xs
[1.309328,1.2439256,1.2039552,1.1766101,1.1564085]

上面显示了随着列表长度的增加,经验的增长顺序,n正如
~ n log n 计算通常显示的那样,这些顺序正在迅速减少。另请参阅此博客文章。快速相关检查:

*Main> let xs = [n*log n | n<- [20, 40, 80, 160, 320, 640]] in 
                                    map (logBase 2) $ zipWith (/) (tail xs) xs
[1.3002739,1.2484156,1.211859,1.1846942,1.1637106]

编辑:
懒惰的评估可以比喻为生产者/消费者习惯用语1,以独立的备注存储为中介。我们编写的任何生产性定义都定义了一个生产者,该生产者将根据其消费者的需求(但不尽早)一点一点地产生其输出。所产生的任何内容都会被记录下来,这样,如果另一个消费者以不同的速度消费相同的输出,它将访问先前填充的相同存储。

当没有更多的消费者引用存储空间时,就会收集垃圾。有时,通过优化,编译器可以完全消除中间存储,从而淘汰中间人。

1另请参见:Oleg Kiselyov,Simon Peyton-Jones和Amr Sabry撰写的Simple Generators v。Lazy
Evaluation

2020-07-28