一个长期被误会的问题,这下说清楚了——迭代与递归的性能

本文探讨了递归与迭代的性能差异,并通过Racket语言解释了递归并不一定导致性能下降的原因。文章还介绍了如何利用尾递归优化来提高递归函数的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个长期被误会的问题,这下说清楚了——迭代与递归的性能

递归真的会比迭代性能差吗?

在《Racket指南》(2.3.4 递归和迭代)中,Racket的作者做了清晰的解释:

在许多语言中,尽可能地将尽可能多的计算合并成迭代形式是很重要的。否则,性能会变差,不太大的输入都会导致堆栈溢出。类似地,在Racket中,有时很重要的一点是要确保在易于计算的常数空间中使用尾递归避免O(n)空间消耗。

然而,在Racket里递归不会导致特别差的性能而且没有堆栈溢出那样的事情;如果一个计算涉及到太多的上下文,你可能耗尽内存,但耗尽内存通常需要比可能触发其它语言中的堆栈溢出更多数量级以上的更深层次的递归。基于这些考虑因素,加上尾递归程序会自动和一个循环一样运行的事实相结合,引导Racket程序员接受递归形式而不是避免它们。

例如,假设你想从一个列表中去除连续的重复项。虽然这样的一个函数可以写成一个循环,为每次迭代记住前面的元素,但一个Racket程序员更可能只写以下内容:

(define (remove-dups l)
  (cond
   [(empty? l) empty]
   [(empty? (rest l)) l]
   [else
    (let ([i (first l)])
      (if (equal? i (first (rest l)))
          (remove-dups (rest l))
          (cons i (remove-dups (rest l)))))]))


> (remove-dups (list "a" "b" "b" "b" "c" "c"))

'("a" "b" "c")

一般来说,这个函数为一个长度为n的输入列表消耗O(n)的空间,但这很好,因为它产生一个O(n)结果。如果输入列表恰巧是连续重复的,那么得到的列表可以比O(n)小得多——而且remove-dups也将使用比O(n)更少的空间!原因是当函数放弃重复,它返回一个remove-dups的直接调用结果,所以尾部调用“优化”加入:

(remove-dups (list "a" "b" "b" "b" "b" "b"))
= (cons "a" (remove-dups (list "b" "b" "b" "b" "b")))
= (cons "a" (remove-dups (list "b" "b" "b" "b")))
= (cons "a" (remove-dups (list "b" "b" "b")))
= (cons "a" (remove-dups (list "b" "b")))
= (cons "a" (remove-dups (list "b")))
= (cons "a" (list "b"))
= (list "a" "b")

基于上述的描述,我们应该认真地重新认识递归的意义和价值,而且,在函数式编程中,你会发现用递归完成很多应用非常简洁,甚至用迭代是难以实现的(会用相对复杂的代码,而且可读性变差)。

而且使用尾递归优化后,更是做到了性能与表达的完美结合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值