从斐波那契数列分析递归与动态规划(JAVA)

来源:互联网 发布:图书馆系统数据库设计 编辑:程序博客网 时间:2024/04/28 14:27

兔子繁殖问题:
这是一个有趣的古典数学问题,著名意大利数学家Fibonacci曾提出一个问题:有一对小兔子,从出生后第3个月起每个月都生一对兔子。小兔子长到第3个月后每个月又生一对兔子。按此规律,假设没有兔子死亡,第一个月有一对刚出生的小兔子,问第n个月有多少对兔子?

相信上面的题目稍微有点经验的程序员都了解过,这就是著名的斐波那契数列(Fibonacci sequence),该数列,又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……
特点:这个数列从第3项开始,每一项都等于前两项之和。

表达式:F[n]=F[n-1]+F[n-2] (n>=2,F[0]=0,F[1]=1)

看到这个表达式相信大多数读者都能想到使用递归算法实现,那么由此我们可以得到求解斐波那契数列的一种算法:

public class Main {    public static int f (int n) {        if (n <= 2) {            return 1;        } else {            return f(n-1) + f(n-2);        }    }    public static void main(String[] args) {        int n = 12;        System.out.println(f(n));    }}

注:若用于求解兔子繁殖问题需要对输入参数进行额外处理

但是!!!
对于小数据来说,上面的算法或许还可行,但是我们发现用30作为输入进行计算的时候,程序输出结果时已经有了一定的时间延迟,而用再大的数据来测试就会发现结果在短时间内根本出不来,这是为什么呢?
对于上面的递归求解方法来说,时间复杂度为O(2^n),所以效率非常慢,为了计算一个f(n),需要存在一个f(n-1)和一个f(n-2),然而f(n-1)递归地对f(n-2)h和f(n-3)进行调用,因此存在两个单独计算f(n-2)的调用。继续跟踪整个算法会发现,f(n-3)被计算了3次,f(n-4)被计算了5次,而f(n-5)则是8次。冗余的计算无疑加重了编译器的负担,如果编译器不能对之前计算过的数据进行保留,这样的增长就无法比避免。
所以这里给出第二种算法,时间复杂度为O(n)

public class Main {    public static int f (int n) {        if (n <= 2) {            return 1;        }        int last = 1;        int nextToLast = 1;        int answer = 1;        //前2位都为1        for (int i = 3; i <= n; i++) {            answer = last + nextToLast;            nextToLast = last;            last = answer;        }        return answer;    }    public static void main(String[] args) {        int n = 12;        System.out.println(f(n));    }}

第二种算法即使用非常大的数据进行测试也仅仅是稍有延迟,短时间内基本可以输出。

下面我们来分析一下这两种算法的思想

分治策略是对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
提到分治策略就不得不提递归,递归与分治的关系网上说的有诸多含糊不清,愚见,分治是一种思想,而递归是实现分治思想的方法

动态规划的基本思想与分治策略类似,区别就在于动态规划是一种带基于记忆化搜素的思想,这也是不同于普通搜索算法的一大特点。
所以,动态规划思想常用于解决问题中含有较多重叠子的问题,这种问题应尽量避免使用单纯的递归算法实现。

阅读全文
0 0
原创粉丝点击