【问题标题】:How do you write an idiomatic Scala Quicksort function?你如何编写一个惯用的 Scala Quicksort 函数?
【发布时间】:2011-02-27 01:35:36
【问题描述】:

我最近回答了question 并尝试在 Scala 中编写快速排序函数,我曾在某处看到类似下面的代码。

def qsort(l: List[Int]): List[Int] = {
  l match {
    case Nil         => Nil
    case pivot::tail => qsort(tail.filter(_ < pivot)) ::: pivot :: qsort(tail.filter(_ >= pivot))
  }
}

我的回答收到了一些建设性的批评,指出 List 是一个糟糕的快速排序集合选择,其次,上述不是尾递归。

我尝试以尾递归的方式重写上面的内容,但运气不佳。是否可以编写尾递归快速排序?或者,如果没有,如何以功能风格完成?还可以做些什么来最大限度地提高实施效率?

【问题讨论】:

    标签: scala


    【解决方案1】:

    几年前,我花了一些时间尝试尽可能优化功能快速排序。以下是我想出的香草List[A]

    def qsort[A : Ordering](ls: List[A]) = {
      import Ordered._
    
      def sort(ls: List[A])(parent: List[A]): List[A] = {
        if (ls.size <= 1) ls ::: parent else {
          val pivot = ls.head
    
          val (less, equal, greater) = ls.foldLeft((List[A](), List[A](), List[A]())) {
            case ((less, equal, greater), e) => {
              if (e < pivot)
                (e :: less, equal, greater)
              else if (e == pivot)
                (less, e :: equal, greater)
              else
                (less, equal, e :: greater)
            }
          }
    
          sort(less)(equal ::: sort(greater)(parent))
        }
      }
      sort(ls)(Nil)
    }
    

    使用自定义 List 结构,我可以做得更好。这种自定义结构基本上跟踪了列表的理想(或接近理想)枢轴点。因此,只需访问这个特殊的列表属性,我就可以在恒定时间内获得更好的枢轴点。在实践中,这比选择头部的标准功能方法要好得多。

    事实上,上面的内容仍然很活泼。它是“半”尾递归(你不能在不变得非常丑陋的情况下进行尾递归快速排序)。更重要的是,它首先从尾部重建,因此与传统方法相比,中间列表的数量大大减少。

    重要的是要注意,这不是在 Scala 中进行快速排序的最优雅或最惯用的方式,它恰好工作得很好。您可能会在编写归并排序方面取得更大的成功,这通常是用函数式语言实现时更快的算法(更不用说更简洁了)。

    【讨论】:

    • 非常感谢您的回答,我认为可以对快速排序进行尾部递归编码的结论非常重要,如果不是,我怀疑可能存在一类算法在 Scala 中是不受限制的.我要再等一会儿,看看有没有其他人回复。
    • 出于兴趣,您为什么使用Ordering 上下文绑定而不是Ordered[A] 视图绑定?与此相关,为什么import ord._ 是必要的?
    • @Ben OrderingOrdered 灵活得多,因为它不会对相关对象的类层次结构施加限制。任何扩展 Ordered 的东西都有一个对应的 Ordering 隐式实例,但反之则不然。 import ord._ 将比较运算符带入A 类型的范围内,这样我就不必执行implicitly[Ordering[A]].gt(e, pivot) 之类的操作。
    • 调用图注释很有意义。习惯上,如果您的问题在逻辑上是多递归的(即每个调用帧有多个递归或相互递归调用),那么您应该以最自然的方式对其进行编码。不要向后弯腰以实现尾递归。拥有它真的很好,但不值得扭曲你的代码来获得。请注意,Scala 2.8 包含一个 trampoline 库,它可以将自然编码的多递归函数转换为尾递归计算。
    • @Daniel 刚刚发现可以使用import Ordered._ 代替import ord 等来允许使用运算符。
    【解决方案2】:

    我想这取决于您所说的“惯用语”是什么意思。快速排序的主要优点是非常快速的就地排序算法。所以,如果你不能就地排序,你就会失去它的所有优势——但你仍然坚持它的 dis 优势。

    所以,这是我为 Rosetta Code 编写的关于这个主题的一些代码。它仍然没有就地排序,但另一方面,它对任何新集合进行排序:

    import scala.collection.TraversableLike
    import scala.collection.generic.CanBuildFrom
    def quicksort
      [T, CC[X] <: Traversable[X] with TraversableLike[X, CC[X]]]      // My type parameters -- which are expected to be inferred
      (coll: CC[T])                                                    // My explicit parameter -- the one users will actually see
      (implicit ord: Ordering[T], cbf: CanBuildFrom[CC[T], T, CC[T]])  // My implicit parameters -- which will hopefully be implicitly available
      : CC[T] =                                                        // My return type -- which is the very same type of the collection received
      if (coll.isEmpty) {
        coll
      } else {
        val (smaller, bigger) = coll.tail partition (ord.lt(_, coll.head))
        quicksort(smaller) ++ coll.companion(coll.head) ++ quicksort(bigger)
      }
    

    【讨论】:

    • 我需要一段时间来了解一些类型参数定义,但我怀疑这是一种惯用的方法,因为该算法在实现中非常明显,而且您的方法也是通用的。也许我被激怒了,认为有一种方法可以避免潜在的堆栈溢出,而无需使用例如蹦床技术或携带大量状态。
    • +1 for “如果你不能就地排序,你就会失去它的所有优势......”
    【解决方案3】:

    碰巧我最近试图解决这个完全相同的问题。我希望将经典算法(即进行就地排序的算法)转换为尾递归形式。

    如果您仍然感兴趣,可以在这里查看我推荐的解决方案:

    Quicksort rewritten in tail-recursive form - An example in Scala

    本文还包含我将初始实现转换为尾递归形式所遵循的步骤。

    【讨论】:

      【解决方案4】:

      我做了一些实验,试图以纯粹的函数式风格编写 Quicksort。这是我得到的(Quicksort.scala):

      def quicksort[A <% Ordered[A]](list: List[A]): List[A] = {
        def sort(t: (List[A], A, List[A])): List[A] = t match {
          case (Nil, p, Nil) => List(p)
          case (l, p, g) =>  partitionAndSort(l) ::: (p :: partitionAndSort(g))
        }
      
        def partition(as: List[A]): (List[A], A, List[A]) = {
          def loop(p: A, as: List[A], l: List[A], g: List[A]): (List[A], A, List[A]) = 
            as match {
              case h :: t => if (h < p) loop(p, t, h :: l, g) else loop(p, t, l, h :: g)
              case Nil => (l, p, g)
            }
      
          loop(as.head, as.tail, Nil, Nil)
        }
      
        def partitionAndSort(as: List[A]): List[A] = 
          if (as.isEmpty) Nil
          else sort(partition(as))
      
        partitionAndSort(list)
      }
      

      【讨论】:

        猜你喜欢
        • 2023-03-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多