Fibonacci数列的定义如下(看百度百科):
F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)
传统方法
首先先看一个传统的基于递归的实现:
public static int fibonacci1(int i) { if (i < 0) { throw new IllegalArgumentException("The number should be not less than 0"); } if (i < 2) { return i; } return fibonacci1(i - 1) + fibonacci1(i - 2); }它有几个明显的问题:
1. 占用空间大
每一次递归的跟进,都会在调用栈上压入当前方法的栈帧。如果递归层次太深就会发生 java.lang.StackOverflowError 错误。例如在我的机器上,当调用 fibonacci1(100000) 时就会发生这种错误(应该更早就发生了溢出了)。
2. 效率低
除了占用空间大,效率低也是一个问题。效率低体现在几方法:
- 方法调用
太多的方法调用本向效率就不高 - 重复多
存在太多的重复计算是影响效率的一个方面。例如fibonacci(4)的计算,当计算fibonacci(5)时需要计算,计算fibonacci(6)的时候又要被计算一次。并且这种重复会产生链式反应,因为每一次的计算又是一次递归。
3. 结果不可靠
最后一个问题就是结果不可靠。fibonacci 数列的值增长很快,很快就能达到 java 中 int 能表示的最大值 (fibonacci(46)),并且也很快就能达到long能表达的数值的上限。也就是说上述实现,最多能计算到46,到47的结果就是不可靠的。
解决方案
使用循环
先来解决问题1和问题2。
通常,每一个递归实现都会有一个相应的循环实现可以替代。可使用循环替代,最关键是每次记录两个值:上个值和上上个值,然后每迭代一次将这两个值更新,如下fibonacci2(int):
public static int fibonacci2(int n){ if (n < 0) { throw new IllegalArgumentException("The number should be not less than 0"); } if (n < 2) { return n; } int f0 = 0; int f1 = 1; for(int i = 2;i<=n;i++){ int f = f0 + f1; f0 = f1; f1 = f; } return f1; }
可以使用以下测试示例比较 fibonacci1(int) 和 fibonacci2(int) 的效率:
for(int i = 0;i< 47;i++){ System.out.println(fibonacci1(i)); //System.out.println(fibonacci2(i)); }
一开始的时候,两者都表现的非常快,但是到后来 fibonacci1(int) 出结果的速度越来越慢,而 fibonacci2(int) 的表现还是一如即往。
使用预定值
如果某些值是频繁读取的,则可以预先把结果存到,例如某个数组里,以后就从数组里读值了。例如:
/** * 构造从 0 - n 对应的 fibonacci 值的数组 */ public static int[] fibonacci(int n) { int[] fArray = new int[n+1]; fArray[0] = 0; fArray[1] = 1; int length = fArray.length; for (int i = 2; i < length; i++) { fArray[i] = fArray[i - 1] + fArray[i - 2]; } return fArray; }
这里根据输入的值 n 构造了每个值对应的 fibonacci 值。以后要想取某个值对应的数列,只要从返回的数组里取即可:
int[] fibonacci = fibonacci(46); for (int i = 0; i < 47; i++) { System.out.println(fibonacci[i]); }
结果的正确
上面说了,当计算到 47 的 fibonacci 值时,结果已经超出了 java 的 int 表示的范围。 方法1就是把int换成long,例如:
public static long fibonacci2(int n){ if (n < 0) { throw new IllegalArgumentException("The number should be not less than 0"); } if (n < 2) { return n; } long f0 = 0; long f1 = 1; for(int i = 2;i<=n;i++){ long f = f0 + f1; f0 = f1; f1 = f; } return f1; }
这次表现好一些,到93才开始出问题。
要保证方法本身的正确性,有两个选择:要么限制可计算的数字的上限;要不扩展类型,让结果可以无限制的大。
限制参数
如果结果是int型,则上限是46:
public static int fibonacci2(int n){ if (n < 0 || n > 46 ) { throw new IllegalArgumentException("The number should be between 0-46"); } if (n < 2) { return n; } int f0 = 0; int f1 = 1; for(int i = 2;i<=n;i++){ int f = f0 + f1; f0 = f1; f1 = f; } return f1; }
如果结果是long型,则上限是92:
public static long fibonacci2(int n){ if (n < 0 || n > 92) { throw new IllegalArgumentException("The number should be between 0-92"); } if (n < 2) { return n; } long f0 = 0; long f1 = 1; for(int i = 2;i<=n;i++){ long f = f0 + f1; f0 = f1; f1 = f; } return f1; }
无上限结果
可以使用BigInteger来产生无限的整数,以避免int或long的上限的问题:
public static BigInteger fibonacci3(int n){ if (n < 0) { throw new IllegalArgumentException("The number should be a positive number"); } if (n < 2) { return BigInteger.valueOf(n); } BigInteger f0 = BigInteger.valueOf(0); BigInteger f1 = BigInteger.valueOf(1); for(int i = 2;i<=n;i++){ BigInteger f = f0.add(f1); f0 = f1; f1 = f; } return f1; }
为了提交效率,可能可以使用分段计算的方式:当n<47时使用基于int的计算方法;当n<93时可以使用基于long的计算方法;否则使用基于BigInteger的计算方法。