在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
| otherwise = (a+c+1,b) : merge xs ((c+1,d):ys)
....
g n = concat [[a,b] | (a,b) <- zip [1,3..n] [n,n-2..1]]
运行它几个列表长度,我们看到它确实是~ 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
| otherwise = (a+1,b) : merge xs ((c+1,d):ys)
这是各种长度列表的结果,
*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。
sort
实施”。很容易写出sort
这样的文章O(n log n)
,但撰写该文章所花费的时间或更短,但并非出于您的考虑。