【问题标题】:Quicksort in SML using foldr使用 foldr 在 SML 中快速排序
【发布时间】:2017-02-08 06:35:50
【问题描述】:

我正在处理一个额外的任务,其中 SML 中快速排序的分区函数只能使用 foldr 完成,并且不能使用库函数。我已经很好地完成了分区,并且快速排序的基础知识也很好,但是我遇到了问题,它似乎将错误的列表合并在一起/以错误的顺序合并。

(*comp is a placeholder, I will be given a comparator function and list as input*)
fun comp a b = a > b;

(*Both partition functions are working as intended.*)
fun getGreater (a::b) c = foldr (fn (a, lst) => if (comp a c) then a::lst else lst) [] (a::b);
fun getLesserOrEqual (a::b) c = foldr (fn (a, lst) => if not (comp a c) then a::lst else lst) [] (a::b);

fun merge (a::b) (c::d) = foldr (op::) (c::d) (a::b);

fun tripleMerge (a::b) c (d::e) = merge (a::b) (c::(d::e));

(*Removes head, uses head as pivot. Recursive calls on results on getLesserOrEqual and getGreater*)
fun sort [] = [] | sort (a::b) = if ([a] = (a::b)) then [a] else
    tripleMerge (sort(getLesserOrEqual b a)) a (sort(getGreater b a));

例如,以下是我正在运行的一些测试。当我在纸上遵循我的逻辑时,我得到的答案与不正确的项目不同:

sort [2, 6, 3, 6, 4, 100, 0];
val it = [0,2,3,6,4,6,100] : int list (*Incorrect*)

sort [1, 2, 3, 4, 5];
val it = [1,2,3,4,5] : int list

sort [5, 4, 3, 2, 1];
val it = [5,4,3,2,1] : int list (*incorrect*)

sort [1, 100, 10, 1000, 0];
val it = [0,1,10,100,1000] : int list

sort [1, 2, 1, 2, 1, 2, 5, 1];
val it = [1,1,1,1,2,2,2,5] : int list

我的错误对任何人来说都是显而易见的吗?

【问题讨论】:

  • 使用您显示的代码,当我运行sort [2, 6, 3, 6, 4, 100, 0]; 时,我收到错误消息uncaught exception Match [nonexhaustive match failure],而不是错误排序的列表。您确定您发布的代码正确吗?

标签: quicksort sml smlnj


【解决方案1】:

我看到的唯一错误是在您的 mergetripleMerge 函数中。通过使用a::b 之类的模式,您将禁止使用空列表。但是 - 当枢轴是最小或最大元素时,您的分区函数之一将返回空列表。如果你像这样调整这两个函数的代码:

fun merge xs ys = folder (op::) ys xs;

fun tripleMerge xs y ys = merge xs (y::ys);

剩下的代码不用管,它会按预期工作。

【讨论】:

  • 感谢您的洞察力。将合并重写为您提供的内容,并将对 TripleMerge 的调用替换为 merge (getLEQ ...) (pivot::(getGreater ...)) 解决了我的问题。
【解决方案2】:

这里有一些反馈:

  1. This isn't Quicksort.

  2. 您可以参考op > (a, b),而不是写fun comp a b = a > b(即将一个运算符变成一个接受一对的函数)。

    我了解您的实际比较函数是作为输入提供的。一个小的混淆点可能是在标准库中,名为 compare 的函数返回 order 类型,即 LESSEQUALGREATER,而不是 bool 。不过没关系。

  3. 正如 John 所说,当函数为空列表明确定义时,没有真正的理由使用模式 a :: b

  4. 在函数名称前加上 get 有点多余,因为所有(纯)函数都以 something 为主要目的。

  5. 您可以通过制作更高阶的变体来组合您的两个过滤功能:

    fun flip f x y = f y x
    fun filter p xs = foldr (fn (x, ys) => if p x then x::ys else ys) [] xs
    fun greaterThan x xs = filter (flip comp x) xs
    fun lessThanOrEqual x xs = filter (comp x) xs
    
  6. sort 似乎有两种基本情况,一种用模式处理,另一种用if-then-else。为什么?

    fun sort [] = []
      | sort [x] = [x]
      | sort (x::xs) = sort (lessThanOrEqual x xs) @ x :: sort (greaterThan x xs)
    

    或者,如果出于某种原因,您希望最后列出基本案例:

    fun sort (x::(xs as (_::_))) = sort (lessThanOrEqual x xs) @
                                   x :: sort (greaterThan x xs)
      | sort xs = xs
    
  7. 尽管this isn't Quicksort 远没有那么高效,但您可以做的一件事来改进它,就是只通过xs 一次,而不是两次。由于我们只允许foldr,因此我们必须生成一对列表:

    fun partition p xs = foldr (fn (x, (ys, zs)) =>
        if p x then (x::ys, zs) else (ys, x::zs)) ([], []) xs
    
    fun sort [] = []
      | sort [x] = [x]
      | sort (x::xs) =
        let val (lesser, greater) = partition (comp x) xs
        in sort lesser @ x :: sort greater end
    

【讨论】:

  • 好帖子,虽然我倾向于同意这个答案(对于你链接到的问题),它说最好将其视为低效的快速排序而不是不是快速排序:stackoverflow.com/a/7718127/4996248
  • @JohnColeman:我倾向于同意 Jon Harrop 在同一线程中的评论:»任何算法的“有效”实现都应该具有相同的渐近边界,你不觉得吗?混蛋的 Haskell 快速排序不会保留原始算法的任何内存复杂性。差远了。这就是为什么它比 Sedgewick 真正的 C 语言 Quicksort 慢 1,000 倍以上。«
  • 我看起来是这样的:如果一个学生跑到我面前兴奋地告诉我他们刚刚发现了自己的排序算法,并且相当于这个 Haskell 版本,那我就失职了指出他们基本上重新发现了快速排序的一个版本。如果我没有指出由于您概述的原因而实施不是很好,我也会失职。归根结底,这是一个定义问题,即这是否算作不好的快速排序或根本不是快速排序。我对这件事没有非常强烈的意见。
  • @JohnColeman:我最近才通过阅读过多的Jon Harrop's blog 获得了对此的强烈看法,并发现他对过度简化但效率低下的 FP 示例的批评令人信服。 Jon 似乎是一个proponent of FP,但也许是因为态度倾向于从 StackOverflow 中删除。他的观点对我来说似乎是正确的:不好的例子给 FP 带来了坏名声。你是对的,有一种分区交换排序,只是不是很快。 ;-)
  • 我对 comp.lang.functional 的 Usenet 时代的 Jon Harrop 很熟悉,并且绝对尊重他的观点,尽管他有时似乎有点教条。
猜你喜欢
  • 1970-01-01
  • 2014-04-17
  • 2021-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-02
  • 2014-04-17
  • 1970-01-01
相关资源
最近更新 更多