学而不思则罔,思而不学则殆
【算法】斐波那契数列
斐波那契数列公式
公式如下:
F
(
n
)
=
{
1
n=0,1
F
(
n
−
1
)
+
F
(
n
−
2
)
n>2
F(n)= \begin{cases} 1 & \text{n=0,1}\\ F(n-1) + F(n-2) & \text{n>2}\\ \end{cases}
F(n)={1F(n−1)+F(n−2)n=0,1n>2
这种很容易有两种解法:递归和非递归
递归一
private static long fac(long n) {
if (n == 0 || n == 1) {
return 1;
}
return fac(n - 1) + fac(n - 2);
}
代码只有几行,很好理解,但是呢,运行效率不怎么行,后面会跟非递归方法对比一下。
递归二
private static long fac2(long n) {
HashMap<Long, Long> map = new HashMap<>();
map.put(0L, 1L);
map.put(1L, 1L);
return fac22(n, map);
}
private static long fac22(long n, HashMap<Long, Long> map) {
if (map.containsKey(n)) {
return map.get(n);
}
long result = fac22(n - 1, map) + fac22(n - 2, map);
map.put(n, result);
return result;
}
该方法也是递归实现,只是会通过map保存已经计算的结果,理论上所有的求值智慧计算一遍就不会再重复计算了。方法对比第一种方法,会减少很多的计算量。减少了第一种方法的重复计算部分,大大减少了时间消耗。
非递归 - 动态规划
动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。
基本思想:问题的最优解如果可以由子问题的最优解推导得到,则可以先求解子问题的最优解,在构造原问题的最优解;若子问题有较多的重复出现,则可以自底向上从最终子问题向原问题逐步求解。
使用条件:可分为多个相关子问题,子问题的解被重复使用
已经求得的子问题可以通过一维数组,二维数组保留下来,使用的时候可以直接获取。
//动态规划
private static long fac3(long n) {
if (n == 1 || n == 0) {
return 1;
}
HashMap<Long, Long> map = new HashMap<>();
map.put(0L, 1L);
map.put(1L, 1L);
long index = 2L;
while (index <= n) {
map.put(index, map.get(index - 1) + map.get(index - 2));
index++;
}
return map.get(n);
}
思路:通过map保存下之前的结果,自底向上求解
三种算法对比
算法\时间 | 递归一 | 递归二 | 动态规划 |
---|---|---|---|
0 | 0 | 1 | 0 |
1 | 0 | 0 | 0 |
10 | 0 | 0 | 1 |
20 | 0 | 1 | 0 |
30 | 4 | 0 | 2 |
40 | 468 | 0 | 0 |
45 | 5003 | 0 | 1 |
46 | 7843 | 0 | 1 |
47 | 12707 | 0 | 1 |
48 | 20056 | 1 | 1 |
49 | 32440 | 0 | 1 |
50 | 52371 | 1 | 1 |
60 | - | 0 | 1 |
70 | - | 1 | 1 |
80 | - | 0 | 0 |
90 | - | 0 | 1 |
100 | - | 1 | 1 |
200 | - | 1 | 1 |
500 | - | 1 | 2 |
1000 | - | 1 | 1 |
可以看到第一种算法当求50的数的时候时间就要50多秒了,后面直接不行了,而第二种和第三种算法几乎一直是很低的一个事件消耗,只是有
O
(
n
)
O(n)
O(n)
的空间消耗。