haskell长度运行时间O(1)或O(n)

时间:2017-10-12 18:01:13

标签: haskell

我正在进行Haskell任务,我正在考虑如何让我的代码更快。 例如,我的下面的因子函数找到某个整数的除数的数量。

factors :: Int -> Int
factors x = length [n | n <- [1..x], mod x n == 0]

但是,我突然意识到,通过避免使用“长度”,我可以使代码更快。

factors :: Int -> Int
factors x = go 1
  where
    go :: Int -> Int
    go i
      | i == x        = 1
      | mod x i == 0  = 1 + go (i + 1)
      | otherwise     = go (i + 1)

我想知道Haskell的长度函数是否为O(n),如C中的strlen()或Java中的String.length()中的O(1)。

此外,编写代码是否更好或更有效?

4 个答案:

答案 0 :(得分:2)

  

此外,编写代码是否更好或更有效?

整数因子分解是最着名的问题之一。肯定已经为此提出了很多算法,即使我不够专业也不能提出建议(CS.SE即将到来,如果需要可以提供帮助)。这些提议都不是多项式时间,但这并不能阻止它们比琐碎的方法更快。

即使不查看文献,也可以找到一些简单的优化。

  • 原始代码会扫描整个列表[1..x],但这不是必需的。我们可以在sqrt x停留,因为此后不再有除数。

  • 更多:在我们找到除数m后,我们可以将x除以m(尽可能多次),然后用这个新数字递归。例如。如果我们尝试x = 1000m=2,我们会计算1000 -> 500 -> 250 -> 125,然后在2中找到新的除数(大于125)。请注意这是如何使数字更小。

我将在Haskell中实施这些策略作为练习:-P

答案 1 :(得分:2)

Willem Van Onsem领导着一个奇怪的,我认为不容易辩护的论点(在某些“理论”意义上我们无法知道length的复杂性,因为编译器可以自由地优化代码和数据结构)。撇开这是否是正确的还是埋没的矛盾,我不认为这是解决这类问题的一种非常富有成效的方法。

通过查看length的定义,您实际上可以推断[a](和许多其他函数)的复杂性:

Prelude> :info []
data [] a = [] | a : [a]    -- Defined in ‘GHC.Types’

列表是归纳定义的;您可以从该定义(几乎只是常规的haskell)中看到,在顶层,列表是构造函数[]:。显然length必须在此结构上递归n次,因此必须是O(n)。

能够以这种方式至少直观地推理是非常重要的,特别是关于无处不在的列表。例如快速(!!)的复杂性是什么?

如果你想在懒惰的情况下深入研究时间复杂性,那么你需要选择Okasaki的“Purely Functional Data Structures”。

答案 2 :(得分:1)

从理论的角度来看,我们无法知道 length是否为θ(n),我们知道它是 O(n),但它从技术上讲,Haskell可以更快地为已知列表实现它。

因为Haskell编译器可以随心所欲地实现列表。但是无所谓,因为在那种情况下生成首先列出的列表将采用θ(n)

请注意,即使编译器使用更专用的数据结构,Haskell也是惰性的,因此 list comprehension 不会产生完整的列表,但更多的是可以的函数em>懒洋洋地生成一个列表。

最后,如果我们急切地评估列表理解,那么它将再次要求 O(n)首先生成列表。因此,即使获得长度非常快,生成列表也需要 O(n)作为下限。因此无论length的效率如何,算法仍将与输入线性对比。

您自己的实现再次使用 O(n)(说实话并不是很安全)。不过,您可以轻松地将数字的因子分解加速到 O(sqrt n)

factors :: Int -> Int
factors x = go 1
  where
    go :: Int -> Int
    go i | i2 > x = 0
         | i2 == x = 1
         | mod x i == 0 = 2 + go (i+1)
         | otherwise = go (i + 1)
        where i2 = i*i

这里我们枚举从1到sqrt(n)。每次我们找到因子a时,我们都知道存在 co <​​/ em> - 因子b = x/a。只要a不等于sqrt(x),我们就知道它们是不同的。如果a等于sqrt(x),我们知道a等于b,因此我们将其视为一个。

话虽如此,肯定有更快的方法。这是一个涉及大量研究的主题,已经产生了更有效的算法。我不是说上面的速度最快,但在时间复杂度方面肯定是一个巨大的改进。

答案 3 :(得分:1)

使用列表推导构建列表已经O(n)。因此,在使用长度函数时没有太多开销,在最坏的情况下,该函数应该具有复杂度O(n)