【问题标题】:Is the computational complexity of this function O(2^n) or O(n)这个函数的计算复杂度是O(2^n)还是O(n)
【发布时间】:2016-06-02 11:28:33
【问题描述】:

我想创建一个函数来创建一个无限列表,该列表接受两个数字和一个运算符作为输入,以便它可以生成算术和几何序列。

infiniteList:: (Floating a)=>a->(a->a->a)->a->[a]
infiniteList start operation changeby = 
  start:[(operation x changeby)| x<-(infiniteList start operation changeby)]

代码编译并正常工作:infiniteList 1 (*) 2 生成一个从 1 开始的列表,后续数字是其前一个数字的两倍。

现在我无法弄清楚“计算列表的第 n 个元素”的计算复杂性。从技术上讲,它正在执行一项操作来找出列表的每个元素。但是,如果您在 (2^k +1) 项之后,我将不得不等待计算机先完成计算 2^(k+1) 个元素。

我希望我能正确解释这一点,所以基本上我认为程序以 2^k 批次生成元素,其中 k 是整数,所以你可能正在等待 (2^(k+1)-2^ k) 计算第 (2^k +1) 个整数的时间。那么“计算列表的第 n 个元素”的计算复杂度是多少?

【问题讨论】:

  • 等一下,我是不是误解了这个函数的工作原理?我认为创建的列表是递归地输入到自身中的。并且每次将其馈入自身时,列表的长度都会增加。我认为列表会加倍,因为 start:[(operation x changeby)| x
  • 经验基准测试表明,正如所写,执行时间是二次方的,即使使用 -O2 编译也是如此。
  • 这无关紧要,但您的 Floating a 上下文完全没用,应该删除。
  • 顺便说一句 - 在标题中你的意思是写 O(n^2) 而不是 O(2^n)?

标签: haskell


【解决方案1】:

一个关键的工具是以下规则:

在分析绑定的性能(不是整体)时,您可以在分析其右侧时假设绑定本身已被全面评估。

您正在定义infiniteList,因此您可以假设在RHS 中,infiniteList 绑定已被完全评估。不幸的是,这没有用,因为infiniteList 只是一个函数,而对它进行全面评估只会给你这个函数!

但是你可以使用这个推理工具来解决问题:你必须绑定正确的东西

infiniteList :: a -> (a -> a -> a) -> a -> [a]
infiniteList start operation changeby =
  let result =
    start : [operation x changeby | x <- result]
  in result

现在您有了一个有用 绑定,result,您可以假设它已经过全面评估!在 RHS 中,您现在基本上拥有

start : map (\x -> operation x changeby) result

显然是O(n)

确实是第一个定义,

> infiniteList 1 (*) 2 !! 10000

花费的时间比我希望的要长,但使用修改后的定义,即使在 GHCi 中也只需要 0.04 秒。

【讨论】:

    【解决方案2】:

    运行时间很大程度上取决于 GHC 决定如何评估它。

    为了简化事情,考虑这个版本的函数:

    inf a f = a : [ f x | x <- inf a f ]
    

    如果 GHC 对 int a f 执行公共子表达式消除,它可以决定评估它,就好像它已经被写入一样:

    inf a f = let r = a : [ f x | x <- r ]
              in r
    

    这是在线性时间内运行的。

    【讨论】:

    • 抱歉 - 在发布我的帖子之前我没有看到您的帖子。但我支持你。
    【解决方案3】:

    我不确定您从哪里得到“批量”的想法。以下是列表前几个元素的抄本。从中,我认为您应该能够弄清楚复杂性。

    列表的第一个元素是什么?它是start,因为infiniteList 被定义为start:[something],并且该表单的任何列表的第一个元素是start

    列表的第二个元素是什么?我们当然需要参考上面列表中的[something] 部分。该子列表的第一个元素是operation x changeby,其中xinfiniteList 的第一个元素。我们已经决定第一个元素是start,所以第二个元素是operation start changeby,这正是我们想要的。我们必须计算什么来获得第二个元素?只是第一个,加上operation

    列表的第三个元素是什么?它是[something] 的第二个元素,即operation x changeby,其中xinfiniteList 的第二个元素。幸运的是,我们刚刚计算出那是什么…… 我们必须计算什么才能得到第三个元素?只是第一个和第二个,加上operation

    虽然它没有直接回答问题,但您应该问自己期望函数具有什么复杂性。需要做多少工作才能获得nth 元素?您的代码实现可能更糟糕,但它可能会帮助您以不同的方式思考代码。

    【讨论】:

      【解决方案4】:

      只是做一些数学运算,假设计算第 n 项需要T(n) 计算,因为

      [(operation x changeby)| x<-(infiniteList start operation changeby)]
      

      建议,我们需要知道子问题T(n-1),而完整列表推导式有n-1操作,然后concatstar:...操作效率高,并且有1计算,所以

      T(n) = T(n-1) + (n - 1) + 1 = T(n-1) + n -> O(n^2)
      

      其实,你可以通过运行一些示例来“感受”时间复杂度。让f n = (infiniteList 0 (+) 1) !! n,然后运行f 10f 100f 1000f 10000,就可以看出区别了。

      通常,当n=1000 立即运行时,n=10000 运行大约 1 或 2 秒,而n=100000 一直运行,通常是 O(n^2)


      顺便说一句,有一个O(n) 方法:

      infi :: a -> (a -> a -> a) -> a -> [a]
      infi x f s = x : infi (f x s) f s
      

      您可以做一些数学运算并运行一些示例来感受差异。

      【讨论】:

        【解决方案5】:

        有时有助于递归的一种策略是将其扩展几次以更好地了解正在发生的事情。让我们试试吧:

        infiniteList start operation changeby = 
        start:[(operation x changeby) | x <-
               start:[(operation x changeby) | x <-
                      start:[(operation x changeby) | x <-
                             start:[(operation x changeby) | x <-
                                    start:[(operation x changeby) | x <- (infiniteList start operation changeby)]]]]]
        

        我们可以看到列表中的第一个元素将是start,正如预期的那样。然后第二个元素将是来自通过operation x changeby 的第一个递归调用的start。第三项会是什么?好吧,这将是第一个递归调用的第二项,所以它将是 start 通过两次调用 operation x changeby 传递的。现在模式出现了!一般来说,infiniteList 的第 n 项将是startoperation x changeby 对其调用了n-1 次。这是相当不幸的,因为正如任何计算机科学专业的学生都知道的那样,1 + 2 + ... + n - 1 = n(n-1)/2 = O(n^2)

        当然,还有一种更有效的方法来编写这个函数。与其将operation x changeby 应用到start n-1 次来得到第n 项,不如只对前一项应用一次?这将为我们提供O(n) 解决方案。例如,我们可以使用unfoldr from Data.List:

        import Data.List (unfoldr)
        
        infiniteList start operation changeby =
            unfoldr (\x -> Just (x, operation x changeby)) start
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-12-13
          • 1970-01-01
          • 2016-04-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-11
          相关资源
          最近更新 更多