显然,这只是我用来理解Haskell的一个例子。我不打算使用冒泡排序进行排序,我通常也不会在二次算法中通过列表优化单次迭代。
我希望在列表已经排序后停止冒泡排序。我写了这段代码,其中bubble
返回,作为元组中的第二项,布尔状态告诉调用者是否进行了交换:
bubble (x:y:ys)
| x > y = (y : fst (bubble (x:ys)), True)
| otherwise = (x : fst (bubble (y:ys)), snd (bubble (y:ys)))
bubble x = (x, False)
bubblesort xs
| snd (bubble xs) = bubblesort (fst (bubble xs))
| otherwise = fst (bubble xs)
在我熟悉的语言中,它会非常低效,因为bubble (y:ys)
和bubble xs
会被重新计算两次而没有明确的记忆。但鉴于Haskell函数没有副作用,我可以假设编译器会优化重复的调用,使我的代码有效吗?
如果没有,那么编写高效冒泡排序的好方法是什么?
我看过the approach检查列表是否已排序:
bubbleSort (x:y:xs) = if sorted thisSort then thisSort else bubbleSort thisSort
where thisSort = (min x y) : bubbleSort ((max x y):xs)
bubbleSort x = x
sorted (x:y:xs) = if x <= y then sorted (y:xs) else False
sorted x = x
但是我试图避免通过列表进行一次额外的迭代(一旦bubbleSort
没有执行交换,就需要再进行一次列表遍历以验证列表是否已排序。)
答案 0 :(得分:6)
在我熟悉的语言中,它会非常低效,因为
bubble (y:ys)
和bubble xs
会被重新计算两次而没有明确的记忆。但鉴于Haskell函数没有副作用,我可以假设编译器会优化重复的调用,使我的代码有效吗?
不,你不能这么认为。这种称为公共子表达式消除的优化可能是一个相当棘手的权衡,因为您正在为空间交换时间。例如,如果重复的表达式会占用大量内存,那么计算它可能需要两次以便您可以立即使用它,而不是仅计算一次并将其留在内存中。出于这个原因,GHC在应用常见的子表达式消除时非常保守。请参阅this question有关与您的示例非常相似的示例,并this one进行深入讨论。
(在您的情况下,当然,不想两次计算bubble (y:ys)
是完全合理的。您可以通过添加辅助定义来回避整个问题,如Lazersmoke's answer所示。)
答案 1 :(得分:3)
使用where
子句:
bubble (x:y:ys)
| x > y = (y : fst (bubble (x:ys)), True)
| otherwise = (x : fst yys, snd yys)
where
yys = bubble (y:ys)
bubble x = (x, False)
bubblesort xs
| snd bxs = bubblesort (fst bxs)
| otherwise = fst bxs
where
bxs = bubble xs
GHC
>