读昨天刚买的《Android应用性能优化》,第一章介绍了斐波那契数列的实现及优化,这是算法方面的问题。
斐波那契数列,又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、……在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)
书中介绍了4种实现,头两种是递归,后面两种是迭代。当然迭代更好,因为递归效率低下,可能导致栈溢出。
public static long computeRecursively(int n) {
if(n > 1) return computeRecursively(n-2) + computeRecursively(n-1);
return n;
}
第一种很好理解,不用多说,后面的三种,很费力的研究了许久。
第二种是对第一种的优化,见下,每次调用少递归调用一次:
public static long computeRecursivelyWithLoop(int n) {
if(n > 1){
long result = 1;
do{
result += computeRecursivelyWithLoop(n-2);
n--;
}while(n>1);
return result;
}
return n;
}
分析:看起来就像是从第n-2个值开始累加到第一个,在额外加上个1,就是第n个值。
F(n) = F(n-2) + F(n-1)每次都把上次分解得到的较大的一个(即每行的最后一个)进行再分解,得到F(n) = sum(F(n-2) + ... +F(0)) + 1
= F(n-2) + F(n-3) + F(n-2)
= F(n-2) + F(n-3) + F(n-4) + F(n-3)
= F(n-2) + F(n-3) + F(n-4) + (F(n-5)+F(n-4))
= F(n-2) + F(n-3) + F(n-4) + F(n-5) + F(n-6) + F(n-5)
= ...
= F(n-2) + F(n-3) + ... + F(2) + F(3)
= F(n-2) + F(n-3) + ... + F(2) + F(1) + F(2)
= F(n-2) + F(n-3) + ... + F(2) + F(1) + F(0) + F(1)
= sum(F(n-2) + ... + F(0)) + 1
剩下的两种迭代实现的方法也是这个求和的原理,方法3如下:
public static long computeIteratively(int n){
if(n > 1){
long a = 0, b = 1;
do {
long tmp = b;
b += a;
a = tmp;
}while(--n>1);
return b;
}
return n;
}
0 1 2 3 4 5 6 7 8
0 1 1 2 3 5 8 13 21
0 a b
1 a b
2 a b
3 a b
4 a b
5 a b
6 a b
7 a b
经历n-1次循环, F(n) = b
方法4每次迭代计算两项,迭代总数与方法3相比少了一半:
根据n奇偶判断初值,如果n是奇数,则a=0, b=1;如果n是偶数则a=1,b=1
public static long computeIterativelyFaster(int n){
if(n>1){
long a, b = 1;
n--;
a = n&1;
n /= 2;
while(n-- > 0){
a += b;
b += a;
}
return b;
}
return n;
}
自己根据方法4写了自以为更容易理解的:
0 1 2 3 4 5 6 7 8
0 1 1 2 3 5 8 13 21
0 a b
1 a b
2 a b
3 a b
4 a b
经历n/2次循环, 如果n为奇数,F(n)=b; 如果n为偶数,F(n)=a
public static long computeIterativelyFaster2(int n){
if(n>1){
long a=0, b = 1;
int evenOdd = n%2;
long tmp=1L<<63;
long cnt=0;//累积器,2倍为最大值
n /= 2;
while(n-- > 0){
a += b;
b += a;
++cnt;
//溢出的时候检测到
if((b&tmp)!=0){
System.out.println("n:"+n+",a:"+a+",b:"+b+",cnn:"+cnt);
break;
}
}
if(evenOdd==0){
return a;
}else{
return b;
}
}
return n;
}
关于溢出:
Long型有64位,除去第一位是符号位,最大值为9223372036854775807。
可容纳的最大的斐波那契数是7540113804746346429,即第92项。
写的方法里做了检测,当溢出的时候,跳出,查看当前的a、b值,及循环次数。
运行结果:
当n=30的时候,
Long_MAX:9223372036854775807
F[30]
832040
5ms
832040
4ms
832040
0ms
832040
0ms
832040
0ms
当n=100的时候(注释掉前两个递归的方法,费时太长,比较的都是迭代的方法)
Long_MAX:9223372036854775807
F[100]
3736710778780434371
0ms
3736710778780434371
0ms
n:4,a:7540113804746346429,b:-6246583658587674878,cnn:46
7540113804746346429
0ms
此时,迭代所用时间都在1ms之内。
自己写的方法里检测到b溢出了,此时循环次数是46次,意味着最大支持的是2倍的92项值7540113804746346429。
3736710778780434371是已经溢出了之后,丢失了符号位得到的数值。
溢出解决,见第二章BigInteger