Lisp.使用递归(Using Recursion)

相比于其它语言,在Lisp中递归扮演着更加重要的角色。这其中可能有三个主要的原因:

1. 函数式编程。递归算法引入副作用的可能性看上去比较小。

2. 递归数据结构。Lisp的隐式指针使得创建递归定义的数据结构变得简单。最普遍的就是列表:一个列表或者是nil,或者是一个cdr为列表的cons。

3. 优雅。Lisp程序员非常关心他们的程序是否漂亮,而递归算法通常比它们的迭代版本更加优雅。

 

学生一开始的时候会经常感到递归难以理解。但是,你不必考虑一个递归函数的所有的调用来判断它是否正确。

 

如果你想写一个递归函数也是一样的。如果你可以描述一个问题的递归解决方案,那么将你的方案翻译成代码也是很直接的。要使用递归来解决问题,你必须要做两件事情:

1. 你必须了解怎样通过将问题分解成有限数量的相似的,但是更小的问题。

2. 你必须了解怎样解决最小版本的问题——基础情况(base case)。

 

如果你做了这些,你就做到了。你知道一个有限的问题最终会被解决,因为每个递归都使得问题更小,并且最小的问题使用了有限数量的步骤。

 

比如,下面这个找出一个列表长度的递归算法,我们在每次递归的时候都会找到小一些列表的长度:

1. 一般情况下,一个列表的长度是一个列表的cdr的长度加上1

2. 一个空列表的长度是0。

当这种描述被翻译成代码时,基本情况必须先出现;但是当构想递归函数的时候,人们通常是从一般情况开始的。

 

前面的描述显式得描述了找到一个列表的长度的方法。当你定义一个递归函数的时候,你必须确认你分解问题的方式会带来更小的子问题。一个普通列表的cdr对于length来说会产生一个较小的子问题,但是一个循环列表的cdr不会。

 

这里有另外两个递归算法的例子。注意在第二个中,我们在每次递归中都将我呢提分成了两个更小的问题:

member:某个东西如果是列表的第一个元素,或者是这个列表的cdr中member,那么这个东西就是这个列表的member。没有东西是一个空列表的member。

copy-tree:一个cons的copy-tree,是一个由它的的car的copy-tree和它的的cdr的copy-tree组成的cons。一个原子的copy-tree是它自身。

 

一旦你能够以这种方式来描述一个算法,写出递归定义就是很小的一步了。

 

一些算法可以很自然地以这种方式表达出来,而有的却不是这样。如果不使用递归来定义ou-copy-tree,那么你可能费尽九牛二虎之力来定义它。另一方面,迭代版本的show-squares相比于递归版本来说,迭代版本的实现可能更容易理解。有时候,在你尝试写代码之前,哪种方式更自然一些可能没有那么明显。

 

如果你关心效率,有其它两个方面需要考虑。第一,尾递归。在有一个好的编译器的情况下,尾递归和循环在速度上应该没有什么差异。然而,如果你将要走出很远才能完成一个函数尾递归,那么使用迭代可能更好一些。

 

另外一点需要铭记在心的是,显而易见的递归算法并不一直是最有效率的。经典的例子是Fibonacci函数,它是以递归的方式定义的,

1. Fib(0) = Fib(1) = 1

2. Fin(n) = Fib(n-1) + Fib(n-2)

但是直译过来的形式

 

(defun fib (n)
  (if (<= n 1)
    1
  (+ (fib (- n 1))
     (fib (- n 2)))))

确实令人毛骨悚然的没有效率。同样的计算一次又一次地被重复。如果你计算(fib 10),那么函数就会计算(fib 9) 和(fib 8)。但是计算(fib 9),它必须又一次计算(fib 8),如此等等。

 

 

这里有一个迭代版本的函数,它和上面的函数产生同样的结果:

 

 

(defun fib (n)
  (do ((i n (- i 1))
    (f1 1 (+ f1 f2))
    (f2 1 f1))
  ((<= i 1) f1)))


迭代版本并不那么清晰,但是更加有效率。在实践中这种事情发生的可能性多大呢?很少见——这就是为什么所有的教科书都使用相同的例子——但是这是我们应该了解的。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值