递归的本质(Essential of Recursive)

最初让我体会递归的就Joe的《Programming Erlang》,随着对计算(computing)的理解,对递归的体会也会有所不同。这里我记录下我的理解过程。

1,理解递归

递归是需要List这种结构的,这也是为什么Lisp本身就是LISt Processor表缩写。数据是表,那么我们可以一个个地处理表中的每个元素。处理方式也颇为简单:

  1. 解决空表的特殊情况。
  2. 处理头元素,一般是一个S-expressions(atom or list)。
  3. 递归处理尾元素。尾元素通常表示为:(cdr lst)。

上面这句话还可以再简练些:

  1. 擦屁股
  2. 处理头
  3. 递归尾

2,递归解构

能达到什么样要求的语言才可以实现递归?

很明显,递归的函数必须接受List作为其参数(arguments),所以必须有类型List这样的数据结构。其次,必须有first, rest这样的no effect side数据返回提取操作。

我们来说说函数,其实函数是具名的Processor,除此之外他还表示高一层的抽象——他是一个处理过程。当一个函数(处理过程)接受List类型时,这个函数离“可递归”不远了。

3,解决问题的递归

记得刚学面向对象时,当某些人说:“一功都是对象”。就会觉得某人牛逼,顿时崇拜有加。但随着时间的深入,有些觉得挺扯蛋的。比如在一些控制操作比较多的应用中,如果你用OOP的话,那些你创建了一堆臆想的对象。这不就是为OOP而OOP嘛。

推理到这里,递归也不适合解决所有问题。但确实有些问题从本质上就是递归的,比如快速排序,用Erlang来表示的话就是:

sort([Pivot|T]) -> 
    sort([ X || X <- T, X < Pivot]) ++ 
    [Pivot] ++ 
    sort([ X || X <- T, X >= Pivot]); 
sort([]) –> [].

从完成任务的思想上来说:

  1. 擦屁股(当list为空时直接返回空)。
  2. 处理头(Pivot)。
  3. 递归尾(T < Pivot和T >= Pivot)。其实这里更通俗的说法是处理剩下的,也就是比Pivot大的和比Pivot小的。

如果用自然语言来说比较绕,也就是把第1个元素当Pivot,然后将比Pivot小的放到Pivot前面,将比Pivot大的放到Pivot后面。怎么知道是否比Pivot大?递归回去直到只有一个元素。

我这里说这么多无非就是递归只是一种抽象方式,也是看世界的一个角度。有时候从这个角度看世界很清晰,有时候根本看不清。可以尝试从另一个角度观察,尽量贴近问题域来解决问题,而不是解决方案域来解决问题。

@ 2012-03-13 08:00

Comments:

Sharing your thoughts: