• Java与算法之(3)


    斐波那契数列问题:如果一对兔子每月能生1对小兔子,而每对小兔在它出生后的第三个月里,又能开始生1对小兔子,假定在不发生死亡的情况下,由一对初生的兔子开始,1年后能繁殖出多少对兔子?

    首先手工计算来总结规律,如下表

    注意总数这一列

    1+1=2

    1+2=3

    2+3=5

    3+5=8

    5+8=13

    可以得出规律,第n个斐波那契数=第n-1个斐波那契数+第n-2个斐波那契数

    为了计算n,必须计算n-1和n-2;为了计算n-1,必须计算n-2和n-3;直到n-x的值为1为止,这显示是递归大显身手的地方。来看代码

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public class Fibonacci {  
    2.     public static long calc(long n) {  
    3.         if(n < 0) {  
    4.             return 0;  
    5.         }  
    6.         if(n == 0 || n == 1) {  
    7.             return n;  
    8.         } else {  
    9.             return calc(n - 1) + calc(n - 2);  
    10.         }  
    11.     }  
    12. }  

    这真是极短的,测试代码

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public static void main(String[] args) {  
    2.     long n = 50;  
    3.     long begin = System.nanoTime();  
    4.     long f = Fibonacci.calc(n);  
    5.     long end = System.nanoTime();  
    6.     System.out.println("第" + n + "个斐波那契数是" + f + ", 耗时" + TimeUnit.NANOSECONDS.toMillis(end - begin) + "毫秒");  
    7. }  

    运行输出

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. 50个斐波那契数是12586269025, 耗时66024毫秒  

    注意看消耗的时间,在我的电脑上耗时66秒,真是个相当耗时的操作。既然整个过程都是在不断重复相同的计算规则,那我们可以采用分而治之的思想来优化代码。

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. import java.util.concurrent.ForkJoinPool;  
    2. import java.util.concurrent.RecursiveTask;  
    3. import java.util.concurrent.TimeUnit;  
    4.   
    5. public class Fibonacci extends RecursiveTask<Long> {  
    6.       
    7.     long n;  
    8.     public Fibonacci(long n) {  
    9.         this.n = n;  
    10.     }  
    11.   
    12.     public Long compute() {  
    13.         if(n <= 10) {  //小于10不再分解  
    14.             return Fibonacci.calc(n);  
    15.         }  
    16.         Fibonacci f1 = new Fibonacci(n - 1);  //分解出计算n-1斐波那契数的子任务  
    17.         f1.fork();  //由ForkJoinPool分配线程执行子任务  
    18.         Fibonacci f2 = new Fibonacci(n - 2);  //分解出计算n-2斐波那契数的子任务  
    19.         return f2.compute() + f1.join();  
    20.     }  
    21.   
    22.     public static long calc(long n) {  
    23.         if(n < 0) {  
    24.             return 0;  
    25.         }  
    26.         if(n == 0 || n == 1) {  
    27.             return n;  
    28.         } else {  
    29.             return calc(n - 1) + calc(n - 2);  
    30.         }  
    31.     }  
    32.       
    33.     public static void main(String[] args) {  
    34.         long n = 50;  
    35.         long begin = System.nanoTime();  
    36.         Fibonacci fibonacci = new Fibonacci(n);  
    37.         ForkJoinPool pool = new ForkJoinPool();  
    38.         long f = pool.invoke(fibonacci);  
    39.         long end = System.nanoTime();  
    40.         System.out.println("第" + n + "个斐波那契数是" + f + ", 耗时" + TimeUnit.NANOSECONDS.toMillis(end - begin) + "毫秒");  
    41.     }  
    42. }  

    运行输出

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. 50个斐波那契数是12586269025, 耗时20461毫秒  

    虽然时间缩短了2/3,但是仍然不理想。回头重新看计算方法,用递归方式虽然代码简短,但是存在很严重的重复计算,下面用非递归的方式改写,过程中每个数只计算一次。

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public static long calcWithoutRecursion(long n) {  
    2.     if(n < 0)  
    3.         return 0;  
    4.     if(n == 0 || n == 1) {  
    5.         return n;  
    6.     }  
    7.     long fib = 0;  
    8.     long fibOne = 1;  
    9.     long fibTwo = 1;  
    10.     for(long i = 2; i < n; i++) {  
    11.         fib = fibOne + fibTwo;  
    12.         fibTwo = fibOne;  
    13.         fibOne = fib;  
    14.     }  
    15.     return fib;  
    16. }  

    测试

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. 50个斐波那契数是12586269025, 耗时0毫秒  

    斐波那契数的另一个经典题目是青蛙跳台阶问题:

    一只青蛙一次可以条一级或两级台阶,求该青蛙跳上n级的台阶共有多少种跳法。

    假设计算第n级台阶跳法的函数是f(n),当n>2时,第一步选择跳一级有X种跳法,第一步选择跳两级有Y种跳法,f(n)=X+Y。如何计算X呢,站在青蛙的位置考虑,面对的是一个全新的n-1级台阶,有f(n-1)种跳法,那么Y就是n-2级台阶的跳法,那么f(n)=f(n-1)+f(n-2),即斐波那契数列公式。

  • 相关阅读:
    linux笔记2.20
    新安装ubuntu后几项配置
    linux网络配置
    linux笔记2.19
    jquery在不同浏览器获取文件路径出现问题!
    软件工程师如何定义自己的职业路线
    标识符
    结对项目-四则运算 “软件”之升级版
    小学四则运算 “软件”之初版
    分布式版本控制系统Git的安装与使用
  • 原文地址:https://www.cnblogs.com/sa-dan/p/6837020.html
Copyright © 2020-2023  润新知