本文基于 Java 描述.
0. 定义
我们熟悉的大多数数学函数都是由一个简单公式来描述的。例如,我们可以利用公式:
C=5(F-32)/9
将华氏温度转换成摄氏温度。
有的时候,数学函数以不太标准的形式来定义,例如,我们可以在非负整数集上定义
一个函数f,它满足:
f(0) =0,且f(x) = 2f(x-1)+x^2.
然后从这个定义我们可以看到
f(1)=2*f(0)+1=1
f(2)=2*f(1)+4=6
f(3)=2*f(2)+9=21
f(4)=2*f(3)+16=58
当一个函数用它自己来定义时就称为是递归(recursive)。
1. 举例
public static int f( int x) {
if ( x==0) //递归退出条件;基准情况
return 0;//基准情况
else
return 2*f(x-1) + x*x;//执行递归调用
}
第2行和第3行处理基准情况 ,即此时函数的值可以直接算出而不用求助递归。正如:
f(x) = 2*f(x-1)+x^2
正如若没有f(0)=0这个事实,那么此f(x)函数在数学上没有意义一样,Java的递归方法若无基准情况也是毫无意义的。
递归可能被混淆的概念:一个常见的问题是:它是否就是循环推理?
答案是: 虽然我们定义一个方法用的是这个方法本身,但是我们并没有用方法本身定义该方法的一个
特定的实例。换句话说,通过使用f(5)来得到f(5)的值才是循环的。通过使用f(4)来得到f(5)的值
不是循环的。当然,除非 f(4)的求值又要用到对f(5)的计算。
2. 无终止的递归方法
public static int bad ( int n) {
if ( n == 0)
return 0;
else
return bad( n/3+1) + n-1;
}
比如你要输入一个n=5, 代码跑一下:
return bad( 5/3+1) + 5-1 --> return bad(2)+4
然后就要找bad(2)
return bad(2/3+1) + 2-1 --> return bad(1)+1
然后就要找bad(1)
return bad(1) +1-1 --> return bad(1)+0
由于这个bad(1)在代码里面没有定义,所以计算机会反复调用bad(1)以期望解出它的值,然后内存被占满,程序崩溃;
3. 总结
以上讨论导致递归的前两个基本法则:
1. 基准情形(base case),必须总要有某些基准的情形,它们不用递归就能求解。
2. 不断推进(marking progress),对于那些要递归求解的情形,递归调用总是能够朝着一个基准情形推进。
本文OVER! 感谢阅读。
本文参考自《Data Structures and Algorithm Analysis in Java》.