【问题标题】:is it possible to do quicksort of a list with only one passing?是否可以只通过一次就对列表进行快速排序?
【发布时间】:2011-10-03 23:51:26
【问题描述】:

我正在学习haskell,看到的函数定义是:

quickSort (x : xs) = (quickSort less) ++ (x : equal) ++ (quickSort more)
                 where less = filter (< x) xs
                       equal = filter (== x) xs
                       more = filter (> x) xs

是否可以只遍历列表一次,而不是 3 次?

【问题讨论】:

  • Quicksort 的平均复杂度为O(n lg n)...
  • 复杂性与比较次数有关,上述版本的比较次数将是通过遍历一次来划分列表的版本的 3 倍。
  • @newacct,它不仅仅是遍历列表;它在遍历时比较每个元素;这就是为什么。
  • @ivanm,是的,复杂度是一样的,平均为 O(n log n)。但它是 O(n^2),最坏的情况。然而,不管这个事实如何,这个问题都是合理的,算法的实现通常是通过常数因子的大小来衡量的。
  • @ivanm,O(n) 表示法是一种衡量复杂性的方法。这并不意味着复杂性是相同的。假设我以英里为单位测量距离,以便于我。这并不意味着我去隔壁和附近的购物中心需要同样的时间。

标签: sorting haskell quicksort difference-lists


【解决方案1】:

你的意思是这样的?

quicksort [] = []
quicksort (x:xs) = quicksort less ++ (x : equal) ++ quicksort more
  where (less, equal, more) = partition3 x xs

partition3 _ [] = ([], [], [])
partition3 x (y:ys) =
  case compare y x of
    LT -> (y:less, equal, more)
    EQ -> (less, y:equal, more)
    GT -> (less, equal, y:more)
  where (less, equal, more) = partition3 x ys

请注意,这并不是真正的快速排序,因为真正的快速排序是就地进行的。

【讨论】:

  • 那很好。我提到的原始算法也没有进行就地快速排序。我假设就地意味着列表被就地修改的可变版本。
  • 这不是应该泄漏空间的“Wadler pair”问题吗? (我已经发布了另一个应该没有这个问题的版本,根据他的技术 IIRC 完成)。
  • @WillNess:您的版本肯定更高效,特别是因为它使用差异列表而不是(++) 进行追加。我的可能会分配更多,因为它使用元组,但我认为它不应该泄漏空间。不过,我不熟悉“Wadler pair”问题。你有链接吗? Google 似乎也不知道。
  • google "Wadler pair space leak" 基本上是关于let (a,b)=span ... 之类的东西,被翻译成let p=span ...; a=fst$p; b=snd$p 这导致p 被保留为b,即使a 被消耗已经。 Pairs... :) (我的意思是,tuples ... IIRC 这正是这个“泄漏”的东西的本意)。
  • 顺便说一句,我认为您的版本是真正的惯用 Haskell 解决方案;太糟糕了,编译器无法自动进行我必须手动编码的转换。我们很自然地用 var 的 元组 来表达并行的“赋值”;这是 Haskell 的错,因为没有明确的概念并强迫我们使用 kludge,然后为此惩罚我们......
【解决方案2】:

它似乎没有任何改善,但:

qs (x:xs) = let (a,b) = partition (< x) xs in (qs a) ++ [x] ++ (qs b)

【讨论】:

  • 太棒了。为什么你认为它没有改善任何东西?它只遍历列表一次。因此,比较要少得多。
  • @qq191:这个定义假设没有重复值,这就是为什么原来有三个过滤器!
  • @JonasDuregård:等等,你是对的;他们进入b 分区。
【解决方案3】:

虽然晚了,但这里的版本应该不会泄漏同样多的空间(并且似乎比这里的其他 3 路版本快两倍):

qsort3 xs = go xs [] 
  where
    go     (x:xs) zs       = part x xs zs [] [] []
    go     []     zs       = zs
    part x []     zs a b c = go a ((x : b) ++ go c zs)
    part x (y:ys) zs a b c =
        case compare y x of
                  LT -> part x ys zs (y:a) b c
                  EQ -> part x ys zs a (y:b) c
                  GT -> part x ys zs a b (y:c)

这解决了使用元组时可能出现的问题,其中let (a,b) = ... 实际上被转换为let t= ...; a=fst t; b=snd t,这导致即使在a 已被消耗和处理之后,它仍然保持活动状态,作为元组t,用于从中读取b - 尽管当然完全没有必要。这被称为“Wadler 对空间泄漏”问题。或者也许 GHC(-O2)比这更聪明。 :)

这显然使用了 差异列表 方法(谢谢,hammar),这也使它更高效(比使用元组的版本快两倍)。我认为part 使用累加器参数,因为它以相反的顺序构建它们。

【讨论】:

  • 参见。 this answer 底部的稳定排序变体。
  • 感谢您提供此解决方案。它帮助我完成了算法课程中的作业,该课程允许在 Haskell 中提交,但有很多对 Haskell 不太友好的作业。这个是为了使用随机枢轴来有效地对具有许多相同元素的列表进行排序,但这对于 Haskell 是不可行的,因为自动分级器不允许非基础导入。这种技术仍然足够快,可以在不到半秒的时间内通过分级机,而无需随机旋转。
  • @arsalanQ 很好,谢谢分享。 :)
猜你喜欢
  • 2010-11-03
  • 1970-01-01
  • 1970-01-01
  • 2020-02-05
  • 1970-01-01
  • 2018-04-20
  • 2013-05-14
  • 2017-08-19
  • 2019-09-26
相关资源
最近更新 更多