递归的本质(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大?递归回去直到只有一个元素。
我这里说这么多无非就是递归只是一种抽象方式,也是看世界的一个角度。有时候从这个角度看世界很清晰,有时候根本看不清。可以尝试从另一个角度观察,尽量贴近问题域来解决问题,而不是解决方案域来解决问题。