经典的算法第四版习题
直进主题:课后习题1.1.19,
Run the following program on your computer:
public class Fibonacci
{
public static long F(int N)
{
if (N == 0) return 0;
if (N == 1) return 1;
return F(N-1) + F(N-2);
}
public static void main(String[] args)
{
for (int N = 0; N < 100; N++)
StdOut.println(N + " " + F(N));
}
}
What is the largest value of N for which this program takes less 1 hour to compute the
value of F(N)? Develop a better implementation of F(N) that saves computed values in an array.(注:N默认为非负数)
计算过程复杂,算到近40个数就已经很慢了,一直在慢慢地递归,
然后做了个小更改:定义了一个大小100的静态数组,并建立了一个相似的f函数,并在函数内部加入判断,如果之前计算过这个值,那么直接调用。(当然每次都需要存下计算结果,如果需要的话)
public static long[] a=new long[100];
public static long f(int N)
{
if (N == 0){
a[0]=0;
return 0;}
if (N == 1){
a[1]=1;
return 1;}
if (a[N-1]!=0){
a[N]=a[N-1]+a[N-2];
return a[N-1]+a[N-2];
}
if (a[N-2]!=0)return f(N-1)+a[N-2];
return f(N-1) + f(N-2);
}
结果秒出(不要在意最后几个的数据溢出了,你可以换成double就好了,
但是作者此处应该是有意为之,让读者自己去关注这些小tips)
计算性能差别上我认为的几个原因:
第一,记录以前的计算结果,减少了重复计算,速度加快(但显然不够充分,因为性能差太多了)
第二,迭代和递归的不同区别,本题的题干即为递归,复杂度大于迭代。这个理由应该可以给出计算机计算的时间差异的主要原因,也就是说,计算机擅长大量并行计算也就是迭代,类似于广度优先。当进行类似于深度优先的计算也就是递归的时候,疲于奔命。
上述都是在说理论,下面说个实在点
第三,编译器前端中的编译器无关代码优化阶段对上述两种方法的处理方式不同。比如3+6=9,由于其值的确定性,代码优化阶段直接替换为9,而碰到了待确定的值时,只能保留原逻辑。这点很关键,因为不仅仅编译如此,计算机的计算更是如此。举例来说:F(99)=F(98)+F(97),而F(98)=F(97)+F(96)
此时F(99)=F(97)+F(96) + F(96)+F(95),这仅仅是第一步,一旦如此进行下去,其复杂程度上升速度以2的N次方的速度攀升,而且这些过程没有计算出任何值,仅仅列出一个等式,就占用了大量内存和运算时间。所以,递归的运行速度之慢可见一斑。
而循环的调用不是必须的,作者这样做应该是为了帮助读者更好地去理解题目本身。一来可以直观地看出计算机计算100个数的过程,方便两种方法效率的比较。二来,才能可以让读者从这100次的计算过程去慢慢体会,迭代计算与递归的具体差别。
而当直接调用f(99)时,计算机并未计算过之前的数值,计算结果一样是秒出的。