Algorithm 两种组合函数的方法,效率有多大不同?

Algorithm 两种组合函数的方法,效率有多大不同?,algorithm,performance,scheme,Algorithm,Performance,Scheme,让f将一个值转换为另一个值,然后我编写一个函数,重复转换n次 我想出了两种不同的方法: 一个是显而易见的 字面上应用函数n 时间,所以重复(f,4)表示x→ f(f(f(x))) 另一种方式是从 快速通电方法,即 把问题一分为二 一半大的问题 只要n是偶数。所以重复(f,4) 表示x→ g(g(x))其中g(x)= f(f(x)) 起初,我认为第二种方法不会提高那么多效率。在一天结束时,我们仍然需要申请f n次,不是吗?在上面的例子中,g仍然会被翻译成fof,而没有任何进一步的简化,对吗 然

让f将一个值转换为另一个值,然后我编写一个函数,重复转换n次

我想出了两种不同的方法:

  • 一个是显而易见的 字面上应用函数n 时间,所以重复(f,4)表示x→ f(f(f(x)))
  • 另一种方式是从 快速通电方法,即 把问题一分为二 一半大的问题 只要n是偶数。所以重复(f,4) 表示x→ g(g(x))其中g(x)= f(f(x))
起初,我认为第二种方法不会提高那么多效率。在一天结束时,我们仍然需要申请f n次,不是吗?在上面的例子中,g仍然会被翻译成fof,而没有任何进一步的简化,对吗

然而,当我尝试这些方法时,后一种方法明显更快


;; computes the composite of two functions
(define (compose f g)
  (lambda (x) (f (g x))))

;; identify function
(define (id x) x)

;; repeats the application of a function, naive way
(define (repeat1 f n)
  (define (iter k acc)
    (if (= k 0)
        acc
        (iter (- k 1) (compose f acc))))
  (iter n id))

;; repeats the application of a function, divide n conquer way
(define (repeat2 f n)
  (define (iter f k acc)
    (cond ((= k 0) acc)
          ((even? k) (iter (compose f f) (/ k 2) acc))
          (else (iter f (- k 1) (compose f acc)))))
  (iter f n id))

;; increment function used for testing
(define (inc x) (+ x 1))
事实上,((repeat2 inc 1000000)0)((repeat1 inc 1000000)0)快得多。我的问题是,第二种方法在哪方面比第一种更有效?重复使用同一功能对象是否保留了存储并减少了创建新对象所花费的时间

毕竟,应用程序必须重复n次,或者用另一种方式说,x→((x+1)+1)不能自动减少到x→(x+2),对吗

我正在运行DrScheme 4.2.1


非常感谢。

两个版本对
inc
的调用次数相同,这是对的,但还有更多 开销大于代码中的开销。具体来说,第一个版本创建了N个闭包,而 第二个只创建日志(N)闭包——如果闭包创建是大部分工作的话 然后,您将看到性能上的巨大差异

有三件事可以让您更详细地了解这一点:

  • 使用DrScheme的
    时间
    特殊表格测量速度。除了时间之外 需要执行一些计算,它还将告诉您在GC中花费了多少时间。 您将看到第一个版本正在做一些GC工作,而第二个版本没有。 (好吧,是的,但它太小了,可能不会显示出来。)

  • 你的
    inc
    函数做的太少了,以至于你只能测量循环开销。 例如,当我使用此错误版本时:

    (define (slow-inc x)
      (define (plus1 x)
        (/ (if (< (random 10) 5)
             (* (+ x 1) 2)
             (+ (* x 2) 2))
           2))
      (- (plus1 (plus1 (plus1 x))) 2))
    
    它不做任何构图,它的工作原理大致相同 与第二个版本的速度相同


  • 第一种方法基本上应用函数n次,因此它是O(n)。但第二种方法实际上并不是将函数应用n次。每次调用repeat2时,只要n为偶数,它就会将n除以2。因此,在大多数情况下,问题的规模会减少一半,而不仅仅是减少1。这给出了O(log(n))的总体运行时


    正如建议的那样,维基百科的文章对它的描述非常清楚。

    这可能有助于维基百科链接在这里并不太相关——这是一种减少算术运算的方法。相似之处在于,
    repeat2
    所做的闭包创建更少,而不是更少的算术工作。在这两种情况下,
    inc
    函数的应用次数相同。在第二个版本中调用较少的是合成函数。(这就是为什么我说更快的运行时间是因为创建更少的闭包。)我同意你的观点,我基本上是这么说的。我们在这里测量的“运算”不是inc中的算术加法,而是compose函数本身。我用对compose函数的调用来表示运行时,算术运算的数量在运行时比较中是不相关的,因为这两种方法中的算术运算数量是相同的。我认为维基百科的文章与这个问题非常相关。观察到的加速是由于第二种方法的对数增长率造成的。好吧,只要这里和维基百科页面中减少的两种开销是不同的,那么实际的方法是相同的。
    (define (repeat3 f n)
      (lambda (x)
        (define (iter n x)
          (if (zero? n) x (iter (sub1 n) (f x))))
        (iter n x)))