最近刚看完SICP第一章,有一些想法,记录下来。
以斐波那契数为例,它的规则是以0、1为前两个数,从第三个数开始,Fib(n)为前两个数之和。
如果要将上述算法进行语言描述,最简单的莫过于递归了。
int Fib(int n)
{
if (n==0) Fib(n)=0;
else if (n==1) Fib(n)=1;
else Fib(n)=Fib(n-1)+Fib(n-2);
return 0;
}
仔细观察,会发现,递归的算法只不过是将问题本身描述了一遍,几乎不用怎么动脑子的。我们用问题本身去解决问题,很神奇,但也很令人不安。
我们只描述了当前一步的情况,把推后要进行的工作统统扔给计算机了,好在每一步的工作都一样。计算机所要做的就是重复的构造出下一步、下下一步直到终于遇见一个具体的数,也就是n=0的时候,它发现到头了,该计算了。然后再从头算起直到n=n。
可以看到实际计算之前,计算机首先要将推后k步所要干的事情描述出来。怎么描述,当然是把它存起来,存到堆栈里去。但是如果这个n是个极大的数的时候,大到超过内存的容量,那么我们这个问题就无法实现了。
是的,递归算法如此容易理解,同时,也存在一个致命的缺陷,就是太占内存。
聪明的我们这个时候就应该想一个办法,如何去克服它的缺陷,如何优化。
回顾一下,递归是从n开始思考的。
而你,遇到这个问题,你是怎么解决的呢?
一般都是从零开始,一个一个的加。我们不会和计算机一样傻,要先把所有要做的东西列出来。是的,太累赘了。但我们做加法的速度太慢,所以让我们把这些烦人的加法甩给计算机吧。它不会抱怨,哪怕是一直重复。
int Fib(int n)
{
int a=0,b=1;
for(int i=0;i<n-1;i++)
{
a=b;
b=a+b;
}
return 0;
}
前人将上述考虑问题的方法定义为迭代。
我的理解是,构造出变量,染后按照一个规则不断更新变量的值。直到它们变为我们想要的值。
上述问题用迭代只需要分配三个字长的内存空间,a、b、n。我们解决了递归问题中耗费内存的问题。
而转折点是,我们洞察了我们是如何思考的,计算机会傻傻地一直构造出一个长堆栈,但我们不会。
《代码大全》里有一句话,“如果我的程序员用递归去求阶乘,我宁愿炒掉他。”
递归对于计算机来说实在是费力不讨好的事情,又占内存,又浪费时间。但是它无疑是我们人脑抽象问题的第一步,最简单的思维模式。
当我们想要解决一个问题的时候,不妨先用递归去思考,然后画出递归树;接下来一定可以发现我们重复了很多操作,比如这里计算Fib(4)的时候要用到Fib(3)的值,而Fib(3)在我们的上一步已经计算过了。所以我们可以用一个变量来存放我们每一步所得到的值,以供下一步所用。逐步地这样去思考,我们就将一个递归的问题变成了迭代。这便体现出计算机里很重要的一个思想,“以空间换时间”。如果你还可以发现迭代问题里的一些废操作,并想方设法将其解决,程序的性能会进一步提高,算法的重要性亦在于此。