【问题标题】:Scheme : recursive process much faster than iterative方案:递归过程比迭代快得多
【发布时间】:2019-08-18 11:06:44
【问题描述】:

我正在研究 SICP,并编写了两个程序来计算 1/n^2 的总和,第一个生成递归过程,第二个生成迭代过程:

(define (sum-rec a b)
  (if (> a b)
      0
      (exact->inexact (+ (/ 1 (* a a)) (sum-rec (1+ a) b)))))

(define (sum-it a b)
  (define (sum_iter a tot)
    (if (> a b)
        tot
        (sum_iter (1+ a) (+ (/ 1 (* a a)) tot))))
  (exact->inexact (sum_iter a 0)))

我测试了两个过程在使用较小的 b 值调用时给出完全相同的结果,并且结果接近 $pi^2/6$,因为 b 变大,正如预期的那样。

但令人惊讶的是,调用 (sum-rec 1 250000) 几乎是瞬时的,而调用 (sum-it 1 250000) 则需要很长时间。

对此有解释吗?

【问题讨论】:

  • 两者不等价——sum_iter 使用精确算术; sum-rec 没有。
  • 这意味着使用bignums会有很多计算,很昂贵

标签: scheme lisp sicp


【解决方案1】:

正如 cmets 中提到的,sum-it 的当前形式是使用精确算术来添加数字,这比sum-rec 中使用的不精确算术要慢。要进行等效比较,您应该这样做:

(define (sum-it a b)
  (define (sum_iter a tot)
    (if (> a b)
        tot
        (sum_iter (1+ a) (+ (/ 1.0 (* a a)) tot))))
  (sum_iter a 0))

请注意,用1.0 替换1 会强制解释器使用不精确的算术。现在这将立即返回:

(sum-it 1 250000)
=> 1.6449300668562465

【讨论】:

    【解决方案2】:

    您可以重新构建这两个版本,以便它们适当地执行精确或不精确的算术,只需控制它们用于零的值并依赖传染规则即可。这两个在 Racket 中,默认情况下没有 1+,但对于带有默认值的可选参数有很好的语法:

    (define (sum-rec low high (zero 0.0))
      (let recurse ([i low])
        (if (> i high)
          zero
          (+ (/ 1 (* i i)) (recurse (+ i 1))))))
    
    (define (sum-iter low high (zero 0.0))
      (let iterate ([i low] [accum zero])
        (if (> i high)
            accum
            (iterate (+ i 1) (+ (/ 1 (* i i)) accum)))))
    

    这样做的好处是您可以很容易地看到两个版本的性能差异。缺点是你需要一个非常聪明的编译器才能优化这里的数值运算(我认为,即使它知道 lowhigh 是机器整数,它也必须推断出 zero 是将是某种数值类型,并为所有可能的类型生成函数体的副本)。

    【讨论】:

      猜你喜欢
      • 2013-09-21
      • 2017-03-08
      • 1970-01-01
      • 1970-01-01
      • 2016-05-09
      • 2015-03-16
      • 2020-12-21
      • 2015-02-27
      • 2019-11-25
      相关资源
      最近更新 更多