【发布时间】:2018-02-06 11:05:11
【问题描述】:
我担心我错过了一些“标准”,但我确实尝试过,老实说!
我一直在尝试了解如何在链表上实现高效的尾递归操作。 (我正在用 Scala 编写,但我怀疑这实际上是否相关)。
注意:我正在从算法理论的角度对此进行研究,我对“使用预建库,它已经解决了”不感兴趣 :)
所以,如果我执行一个简单的过滤器实现来作用于一个列表:
def filter[T](l: List[T])(f: T => Boolean): List[T] = l match {
case h :: t => if (f(h)) h :: filter(t)(f) else filter(t)(f)
case _ => List()
}
这不是尾递归,但它的效率相当高(只要它只是将新项目添加到它构造的结果列表中)
但是,我想出了一个简单的尾递归变体:
def filter[T](l: List[T])(f: T => Boolean): List[T] = {
@tailrec
def filterAcc[T](l: List[T], f: T => Boolean, acc: List[T]): List[T] = l match {
case List() => acc
case h :: t => if (f(h)) filterAcc(t, f, h :: acc) else filterAcc(t, f, acc)
}
filterAcc(l, f, List())
}
颠倒项目的顺序。 (当然,这并不奇怪!)
当然,我可以通过将过滤后的项目 附加 到累加器来修复顺序,但这将使我相信这是一个 O(n^2) 实现(因为每个附加都会强制构建一个全新的列表,它是对 Scala 不可变列表的 O(n) 操作,对列表中的 n 个元素进行 n 次重复)
我也可以通过在生成的列表上调用 reverse 来解决这个问题。我很欣赏这将是一个单一的 O(n) 操作,因此整体时间复杂度仍然是 O(n),但它看起来很难看。
所以,我的问题就是这个;是否有一个尾递归的解决方案,and 从一开始就以正确的顺序累积,即 O(n) 并且可能比“向后捕获并反转它”选项涉及的工作更少?我错过了什么吗(这对我来说很正常,我担心:()
【问题讨论】:
-
之后反转是相当标准的。不知道你看到什么“丑陋”。或者,使用
Queue或Vector之类的东西,因为它们提供(大部分)恒定时间追加。 -
好吧,如果没有必要,那就太丑了:) 但如果有必要,那就这样吧!
-
可以这样看。过滤本身需要单次遍历(如果您有一个带有可变链接的单链表,您可以追加到末尾,单次遍历就足够了)。但是,您想要构建一个不可变数据结构作为结果。第二次遍历(用于反转)可以看作是为获得一个好的、不可变的、线程安全的、持久的数据结构而付出的代价。所以,这通常是值得的。
标签: algorithm scala recursion tail-recursion