10.3.1 递归的概念
在数学和计算机科学中,递归是一种思路和策略,可以用于术语的定义(什么是表达式),问题的描述和问题求解。
作为一种思路和策略,递归与分而治之(divide-and-conquer)互为因果。递归
- 可以用于术语的定义(如什么是表达式),
- 可以用于问题的描述(数学中的分段函数),
- 可以用于问题求解(编程中的递归调用)。称为递归算法,简称递归法,后面直接叫递归。
递归是指用自己的较简单的情形定义自己。有一个故事。物理学家计算10!时会说,“看,它等于1*2*~*10,即3628800”;数学家则说:“哦,10的阶乘,它等于10乘以9!”。
递归算法“轻率地”认为自己的较简单的情形是已知的。既然fact(n-1)是已知的,因而fact(n) 可求。这样“轻率”对理解递归概念至关重要。递归法不直接解决问题,而是将问题变成一个趋向递归出口的问题。使用递归方法需要存在一个基准情形,以避免无限循环(狗追自己的尾巴)。
package algorithm.recursion;
public class RecursionDemo{
/**
* 递归求Fibonacci级数的第n个元素,n基于1的自然数。
*/
public static int fibonacc(int n){
if(n<=1) return n;//base case
else return fibonacc(n-1)+fibonacc(n-2);
}
/**
* 迭代求Fibonacci级数的第n个元素,n基于1的自然数。
*/
public static int fibonacc1(int n){
int first , second ,result ;
first =second=result= 1;
for(int i=3;i<=n ;i++){
result = first + second;
first = second;
second =result;
}
return result;
}
}
大多数情况下,迭代法和递归法能够相互转化。
使用递归法有2条实践准则:
1、设计优先。在任何情况下都可以采用递归法,简洁而清晰的程序设计优先。某些问题,例如那些需要后退的问题(如找出迷宫的出路、对树的一些操作),如果不采用递归则很难解决。
2、 效率平衡。如果递归调用中出现重复性工作,改用循环。对于一般的数值计算,递归法通常不合适。Java递归方法是通过方法调用栈实现的。在BlueJ中设置断点运行factorial (5),将显示方法调用情况。它仅仅“轻率地认为”factorial (4)已知,依此类推。到factorial (5),目前没有进行任一乘法计算。方法调用栈中有6个栈帧,顶层将计算factorial (0)。递归的方法的两个阶段是递推和回归。
例如使用递归式sum(n) =n + sum(n-1),yqj2065看见过一个趣题。
static long sum1(long a) {
return (a == 1)? 1:(a + sum1(a - 1));
}
static long sum2(long a) {
return (a == 1)? 1:(sum2(a - 1) + a);
}
两者有区别吗?【注:在Java7时sum1(6000)StackOverflowError,Java8到大约13000才溢出。
原因不明。】
《编程导论(Java)·10.3.2 案例:汉诺塔问题》
练习:
1.Given a non-negative int n, return the sum of its digits recursively (no loops). Note that mod (%) by 10 yields the rightmost digit (126 % 10 is 6), while divide (/) by 10 removes the rightmost digit (126 / 10 is 12).
sumDigits(126) → 9
sumDigits(49) → 13
sumDigits(12) → 3
类似的: 递归求一个非负int包含的5的个数。
2.小朋友排排坐,单号伸出2手指,双号伸出3手指,递归求n个小朋友时手指的总数。
sum(0) → 0
sum(1) → 2
sum(2) → 5