前言
斐波那契 相信大多数开发者都有所了解,也被人成为“兔子数列”。就是像这样的数列:0、1、1、2、3、5、8、13、21、34、55
如果您还没有想起或者以前没有接触过,可以参考链接:斐波那契百度百科 斐波那契维基百科
好了现在假设你知道了斐波那契的现象,那么进入正题:如何牛逼的用Java语言实现斐波那契
a)最常想到的算法
/**
* 最常想到的 递归
* @param n
* @return
*/
private static long compute(int n){
if(n>1)return compute(n-2)+compute(n-1);
return n;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
long old = System.currentTimeMillis();
System.out.println("结果 :"+compute(10));
System.out.println("耗时 : "+(System.currentTimeMillis()-old));
}
当n数量级为 10时,耗时是一瞬间
b)就在a递归的基础上进行优化
/**
* 稍微优化了的递归,减少了调用次数
* @param n
* @return
*/
private static long computeWithLoop(int n){
if(n>1){
long result = 1;
do {
result += computeWithLoop(n-2);
n--;
} while (n>1);
return result;
}
return n;
}
调用后发现:
虽然调用自身方法的次数少了多次,比如当n为30时 a方案调用本身2692537次,b方案调用本身“只有”1346269次
但是,对用户而言此方案的耗时依然无法接受
c)从递归到迭代
/**
* 迭代算法 线性的速度很快
*/
private static long computeWithDieDai(int n){
if(n>1){
long a = 0,b = 1;
do {
long temp = b;
b += a;
a = temp;
} while (--n>1);
return b;
}
return n;
}
调用后发现 这个算法碉堡了,不管多达数 几乎都是瞬间的事情
耗时 : 272
d)优化后的迭代算法:
/**
* 飞快的优化过的迭代算法
* @param n
* @return
*/
private static long computeWithDieDaiFaster(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;
}
调用后发现
耗时 : 69
e)用BigInteger解决溢出问题
/**
* 用BigInteger解决大数据溢出问题
* @param n
* @return
*/
public static BigInteger computeWithBig(int n){
if(n>1){
BigInteger a,b = BigInteger.ONE;
n--;
a = BigInteger.valueOf(n & 1);
n /=2;
while (n-->0) {
a = a.add(b);
b = b.add(a);
}
return b;
}
return (n ==0) ? BigInteger.ZERO : BigInteger.ONE;
}
long old = System.currentTimeMillis();
System.out.println("结果 :"+computeWithBig(10000));
System.out.println("耗时 : "+(System.currentTimeMillis()-old));
结果 :33644764876431783266621612005107543310302148460680063906564769974680081442166662368155595513633734025582065332680836159373734790483865268263040892463056431887354544369559827491606602099884183933864652731300088830269235673613135117579297437854413752130520504347701602264758318906527890855154366159582987279682987510631200575428783453215515103870818298969791613127856265033195487140214287532698187962046936097879900350962302291026368131493195275630227837628441540360584402572114334961180023091208287046088923962328835461505776583271252546093591128203925285393434620904245248929403901706233888991085841065183173360437470737908552631764325733993712871937587746897479926305837065742830161637408969178426378624212835258112820516370298089332099905707920064367426202389783111470054074998459250360633560933883831923386783056136435351892133279732908133732642652633989763922723407882928177953580570993691049175470808931841056146322338217465637321248226383092103297701648054726243842374862411453093812206564914032751086643394517512161526545361333111314042436854805106765843493523836959653428071768775328348234345557366719731392746273629108210679280784718035329131176778924659089938635459327894523777674406192240337638674004021330343297496902028328145933418826817683893072003634795623117103101291953169794607632737589253530772552375943788434504067715555779056450443016640119462580972216729758615026968443146952034614932291105970676243268515992834709891284706740862008587135016260312071903172086094081298321581077282076353186624611278245537208532365305775956430072517744315051539600905168603220349163222640885248852433158051534849622434848299380905070483482449327453732624567755879089187190803662058009594743150052402532709746995318770724376825907419939632265984147498193609285223945039707165443156421328157688908058783183404917434556270520223564846495196112460268313970975069382648706613264507665074611512677522748621598642530711298441182622661057163515069260029861704945425047491378115154139941550671256271197133252763631939606902895650288268608362241082050562430701794976171121233066073310059947366875
耗时 : 16
f)基于斐波那契Q-矩阵减少内存分配
/**
* 通过斐波那契Q矩阵的公式减少生成的对象
* @param n
* @return
*/
private static BigInteger computeWithBigFaster(int n){
if(n>92){
int m = (n/2)+(n & 1);
BigInteger fM = computeWithBigFaster(m);
BigInteger fM_1 = computeWithBigFaster(m-1);
if((n&1)==1){
return fM.pow(2).add(fM_1.pow(2));
}else{
return fM_1.shiftLeft(1).add(fM).multiply(fM);
}
}
return BigInteger.valueOf(computeWithDieDaiFaster(n));
}
由于BigInteger是不可变的,我们必须写a = a.add(b),而不是简单地用a.add(b),很多人误以为a.add(b)相当于a += b, 但实际上它等价于a + b。因此,我们必须写成a = a.add(b),把结果值赋给a。这里有个小细节是非常重要的:a.add(b)会创建一个新的BigInteger对象来持有额外的值。
目前BigInteger的内部实现,每分配一个BigInteger对象就会另外创建一个BigInt对象。在执行方案e时,要分配两倍的对象:调用computeWithBig(50000)时约创建了100 000个对象(除了其中的1个对象外,其他所有对象立刻变成等待回收的垃圾)。此外,BigInt使用本地代码,而从Java使用JNI调用本地代码会产生一定的开销。
所以本方案基于斐波那契Q矩阵有以下公式:
上面的代码就是根据这个公式写出的,虽然得出的结果依旧没有迭代的方法快,但是毕竟是朝正确的方向又迈出了一步。
最后既然基本类型Long可以容纳小于等于92的结果,我们稍微修改递归实现,混合BigInteger和基本类型(对我启发就是很多优秀的实现都不仅仅是单一的算法,因为各有优缺点,终于领悟以前上学时候的算法为什么一种一种又一种了),且看下面的综合算法
g)混合使用基本类型和BigInteger
/**
* 混合使用基本类型和BigInteger优化
* @param n
* @return
*/
private static BigInteger computeWithBigFaster(int n){
if(n>92){
int m = (n/2)+(n & 1);
BigInteger fM = computeWithBigFaster(m);
BigInteger fM_1 = computeWithBigFaster(m-1);
if((n&1)==1){
return fM.pow(2).add(fM_1.pow(2));
}else{
return fM_1.shiftLeft(1).add(fM).multiply(fM);
}
}
return BigInteger.valueOf(computeWithDieDaiFaster(n));
}
调用后发现
h)通过缓存已经计算过的数据提高性能
/**
* 每次计算过的数据存入缓存
* @param n
* @return
*/
private static BigInteger computeWithCache(int n){
HashMap<Integer,BigInteger> cache = new HashMap<Integer,BigInteger>();
return computeWithCache(n,cache);
}
private static BigInteger computeWithCache(int n,HashMap<Integer, BigInteger>cache){
if(n>92){
BigInteger fN = cache.get(n);
if(fN==null){
int m = (n/2)+(n & 1);
BigInteger fM = computeWithCache(m, cache);
BigInteger fM_1 = computeWithCache(m-1, cache);
if((n&1)==1){
fN = fM.pow(2).add(fM_1.pow(2));
}else{
fN = fM_1.shiftLeft(1).add(fM).multiply(fM);
}
cache.put(n, fN);
}
return fN;
}
return BigInteger.valueOf(computeWithDieDaiFaster(n));
}
因为Java作为编程语言,你可能打算使用一个HashMap充当缓存,它可以胜任这项工作。不过,
Android定义了SparseArray(稀疏数组)类,当键是整数时,它比HashMap效率更高。因为HashMap使用的是java.lang.Integer 对象,而SparseArray使用的是基本类型int。因此使用HashMap会创建很多Integer对象,而使用SparseArray则可以避免
。