短路排序

时间:2009-12-01 21:27:11

标签: sorting haskell lazy-evaluation

我理解:

head (map (2**) [1..999999])

实际上只会评估2 ** 1,其余的都没有,但我正在阅读的那本书说:

head (sort somelist)

只需要找到列表中的最小项目,因为这就是所有使用的项目。这是如何运作的?据我所知,使用我所知的排序算法(如气泡排序)这是不可能的。

我认为这样做的唯一方法是,如果排序算法要遍历整个列表,寻找最小的项目,然后在没有该项目的情况下递归列表。对我来说,这听起来很慢。

这是排序功能的工作方式,还是有其他我不知道的排序算法,可以像它那样短路?

3 个答案:

答案 0 :(得分:10)

此:

  

只需要找到列表中最小的项目,因为这就是所有使用的项目。

...应该说该函数只需要完成最小量的工作,排序算法需要来找到最小的元素。

例如,如果我们使用quicksort作为基础排序算法,那么head . quicksort等同于最佳(!)选择算法,称为“quickselect”,这是最坏情况的线性。此外,我们只能通过take k . quicksort实现 k -quickselect。

维基百科在其关于选择算法的文章中指出(我的重点):

  

由于语言对排序的支持越来越普遍,因此尽管在速度上存在缺点,但在许多环境中,首选的是简单的排序方法,然后进行索引。 的确,对于懒惰的语言,如果您的排序足够懒,这种简单的方法甚至可以为k最小/最大排序(最大/最小作为特殊情况)提供最佳复杂性。

Quicksort在这种情况下工作得很好,而Haskell中的默认排序(合并排序)也不是很好,因为它返回的工作量超过了返回排序列表中每个元素的严格要求。正如this post on the Haskell mailing list所述:

  lazy quicksort能够生成批量的   

中的前k个最小元素      

O(n + k log k)总时间[1]

     

而懒惰的mergesort需要

     

O(n + k log n)总时间[2]

有关详情,请阅读this blog post

答案 1 :(得分:6)

如果你创建一个跟踪其参数的比较函数,就像在GHCi的命令行中那样:

> :module + Data.List Debug.Trace
> let myCompare x y = trace ("\tCmp " ++ show x ++ " " ++ show y) $ compare x y

然后你可以自己看看这个行为:

> sortBy myCompare "foobar"

"     Cmp 'f' 'o'
      Cmp 'o' 'b'
      Cmp 'f' 'b'
      Cmp 'a' 'r'
      Cmp 'b' 'a'
a     Cmp 'b' 'r'
b     Cmp 'f' 'o'
      Cmp 'f' 'r'
f     Cmp 'o' 'o'
      Cmp 'o' 'r'
o     Cmp 'o' 'r'
or"

Haskell懒洋洋地评估字符串,一次一个字符。在找到每个字符时打印左侧列,右侧列记录所需的比较,由“跟踪”打印。

请注意,如果您编译它,特别是在启用优化时,您可能会得到不同的结果。优化器运行一个严格性分析器,可能会注意到整个字符串被打印出来,因此热切地评估它会更有效。

然后尝试

> head $ sortBy myCompare "foobar"

      Cmp 'f' 'o'
      Cmp 'o' 'b'
      Cmp 'f' 'b'
      Cmp 'a' 'r'
      Cmp 'b' 'a'
'a'

如果您想了解其工作原理,请查看sort函数的源代码,并在纸上手动评估'sort“foobar”。

qsort [] = []
qsort (x:xs) = qsort less ++ [x] ++ qsort greater
   where (less, greater) = partition (< x) xs

所以

   qsort ('f':"oobar")
 = qsort ('b':"a") ++ "f" ++ qsort ('o':"or")
 = ("a" ++ "b") ++ "f" ++ qsort ('o':"or")

现在我们已经做了足够的事情,发现'a'是结果中的第一项,而不必评估对“qsort”的另一个调用。我省略了实际比较,因为它隐藏在对“分区”的调用中。实际上“分区”也是懒惰的,所以事实上其他“qsort”的论据并没有根据我的表现进行评估。

答案 2 :(得分:2)

您刚才描述的算法有一个特定的名称:“选择排序”。它是O(n 2 )所以它不是你能做的最快的事情。但是,如果你想要排序数组中的第一个“k”元素,那么复杂性将是O(kn),如果“k”足够小(如你的例子),那就很好。

请注意,您在函数式语言中使用纯函数。通过查看函数的组合方式,编译器很可能在两种情况下都能为sort生成优化代码。在撰写headsort时,您可以很容易地推断出您想要最小元素。