Algorithm 惰性评估与时间复杂性

Algorithm 惰性评估与时间复杂性,algorithm,sorting,haskell,lazy-evaluation,time-complexity,Algorithm,Sorting,Haskell,Lazy Evaluation,Time Complexity,我环顾了一下stackoverflow,这让我看到了Keegan McAllister的演示文稿:。在幻灯片8中,他展示了最小函数,定义为: minimum = head . sort 并指出其复杂性为O(n)。我不明白,如果替换排序是O(nlogn),为什么复杂性会被称为线性的。文章中提到的排序不能是线性的,因为它不对数据做任何假设,因为线性排序方法(如计数排序)需要这样做 懒惰评估在这里扮演着神秘的角色吗?如果是这样的话,背后的解释是什么?这并不神秘。要交付第一个元素,您需要对列表进行多少

我环顾了一下stackoverflow,这让我看到了Keegan McAllister的演示文稿:。在幻灯片8中,他展示了最小函数,定义为:

minimum = head . sort
并指出其复杂性为O(n)。我不明白,如果替换排序是O(nlogn),为什么复杂性会被称为线性的。文章中提到的排序不能是线性的,因为它不对数据做任何假设,因为线性排序方法(如计数排序)需要这样做


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

这并不神秘。要交付第一个元素,您需要对列表进行多少排序?您需要找到最小元素,这可以很容易地在线性时间内完成。碰巧,对于
排序的某些实现,惰性求值将为您执行此操作。

In
minimum=head。排序
排序
不会完全完成,因为它不会预先完成。
排序
只会按照
的要求产生第一个元素

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

mergesortBy小于[]=[]
mergesortBy less xs=head$until(null.tail)对[[x]| x假设
minimum':(Ord a)=>[a]->(a[a])
是一个函数,它返回列表中最小的元素,同时删除该元素。显然这可以在O(n)时间内完成。如果您将
排序定义为

sort :: (Ord a) => [a] -> [a]
sort xs = xmin:(sort xs')
    where
      (xmin, xs') = minimum' xs
然后,延迟计算意味着在
(head.sort)xs
中只计算第一个元素。正如您所看到的,这个元素就是成对
最小值“xs
,它是在O(n)时间内计算的


当然,正如delnan指出的,复杂性取决于
sort

的实现。解释取决于
sort
的实现,对于某些实现,它将不正确。例如,对于在列表末尾插入的插入排序,延迟求值没有帮助。因此,让我们选择一个imp要查看的元素,为了简单起见,让我们使用选择排序:

sort [] = []
sort (x:xs) = m : sort (delete m (x:xs)) 
  where m = foldl (\x y -> if x < y then x else y) x xs
排序[]=[]
排序(x:xs)=m:sort(删除m(x:xs))
其中m=foldl(\x y->如果x

该函数显然使用O(n^2)时间对列表进行排序,但是由于
head
只需要列表的第一个元素,
sort(delete x xs)
从未被计算过!

在实践中看到这一点的一个有趣方法是跟踪比较函数

import Debug.Trace
import Data.List

myCmp x y = trace (" myCmp " ++ show x ++ " " ++ show y) $ compare x y

xs = [5,8,1,3,0,54,2,5,2,98,7]

main = do
    print "Sorting entire list"
    print $ sortBy myCmp xs

    print "Head of sorted list"
    print $ head $ sortBy myCmp xs
首先,请注意整个列表的输出与跟踪消息交错的方式。其次,请注意仅计算头部时跟踪消息是如何相似的

我刚刚通过Ghci运行了这个,它不完全是O(n):需要15次比较才能找到第一个元素,而不是应该需要的10个元素。但是它仍然小于O(n logn)


编辑:正如维图斯在下面指出的那样,用15次而不是10次比较并不等于说它不是O(n)。我的意思是,它需要的比理论上的最小值还要多。

你已经得到了大量的答案,可以解决
head.sort
的具体问题。我只需要添加几个更一般的陈述

通过积极评估,各种算法的计算复杂性以简单的方式组合。例如,最小上界(LUB)对于
f.g
必须是
f
g
的润滑油的总和。因此,您可以将
f
g
视为黑匣子,专门根据它们的润滑油进行推理

然而,在惰性评估中,
f.g
的LUB比
f
g
的LUB之和要好。不能使用黑盒推理来证明LUB;必须分析实现及其交互

因此,一个经常被引用的事实是,惰性计算的复杂性比急切计算的复杂性更难推理。请考虑以下几点。假设您正试图提高一段代码的渐近性能,其形式为
f.g
。在急切语言中,有一个明显的策略可以遵循:pi勾选较复杂的
f
g
,并首先改进这一项。如果你在这方面取得成功,你就成功地完成了
f.g
任务

另一方面,在惰性语言中,您可能会遇到以下情况:

  • 您改进了更复杂的
    f
    g
    ,但是
    f.g
    没有改进(甚至变得更糟)
  • 你可以用一些没有帮助(甚至没有恶化)的方法来改进
    f.g
    f
    g

受Paul Johnson答案的启发,我绘制了两个函数的增长率。首先,我修改了他的代码,每次比较打印一个字符:

import System.Random
import Debug.Trace
import Data.List
import System.Environment

rs n = do
    gen <- newStdGen
    let ns = randoms gen :: [Int]
    return $ take n ns

cmp1 x y = trace "*" $ compare x y
cmp2 x y = trace "#" $ compare x y

main = do
    n <- fmap (read . (!!0)) getArgs
    xs <- rs n
    print "Sorting entire list"
    print $ sortBy cmp1 xs

    print "Head of sorted list"
    print $ head $ sortBy cmp2 xs
运行脚本将显示以下图表:

下面这条线的坡度是

>>> import numpy as np
>>> np.polyfit(x, map(lambda x:x[1], res), deg=1)
array([  1.41324057, -17.7512292 ])

…1.41324057(假设它是一个线性函数)

请注意,该幻灯片正确地陈述了“为了仔细的
排序
实现”。很容易编写一个
排序
,此组合需要
O(n log n)
时间或更糟的时间,但不是出于您认为的原因。您已经阐述了您的观点,但请注意您的排序不是O(n log n),但我明白了;@LeonardoPassos这就是这个答案的美妙之处。:)这是一个选择排序,O(n^2),但它在O(n)时间内产生最小值。这个例子有点误导,因为我们想使用
(head.sort)
作为O(n)中的最小值函数,但您的排序需要这样一个最小值函数。获得O(n)更有趣sor的最小值
sort :: (Ord a) => [a] -> [a]
sort xs = xmin:(sort xs')
    where
      (xmin, xs') = minimum' xs
sort [] = []
sort (x:xs) = m : sort (delete m (x:xs)) 
  where m = foldl (\x y -> if x < y then x else y) x xs
import Debug.Trace
import Data.List

myCmp x y = trace (" myCmp " ++ show x ++ " " ++ show y) $ compare x y

xs = [5,8,1,3,0,54,2,5,2,98,7]

main = do
    print "Sorting entire list"
    print $ sortBy myCmp xs

    print "Head of sorted list"
    print $ head $ sortBy myCmp xs
import System.Random
import Debug.Trace
import Data.List
import System.Environment

rs n = do
    gen <- newStdGen
    let ns = randoms gen :: [Int]
    return $ take n ns

cmp1 x y = trace "*" $ compare x y
cmp2 x y = trace "#" $ compare x y

main = do
    n <- fmap (read . (!!0)) getArgs
    xs <- rs n
    print "Sorting entire list"
    print $ sortBy cmp1 xs

    print "Head of sorted list"
    print $ head $ sortBy cmp2 xs
import matplotlib.pyplot as plt
import numpy as np
import envoy

res = []
x = range(10,500000,10000)
for i in x:
    r = envoy.run('./sortCount %i' % i)
    res.append((r.std_err.count('*'), r.std_err.count('#')))

plt.plot(x, map(lambda x:x[0], res), label="sort")
plt.plot(x, map(lambda x:x[1], res), label="minimum")
plt.plot(x, x*np.log2(x), label="n*log(n)")
plt.plot(x, x, label="n")
plt.legend()
plt.show()
>>> import numpy as np
>>> np.polyfit(x, map(lambda x:x[1], res), deg=1)
array([  1.41324057, -17.7512292 ])