计算机中的黑魔法:尾递归(3)

到此为止,我们就知道,这个计算进程是一个递归计算进程,因为他在计算的进程当中需要耗费资源来保存所需的一些情况和信息。递归的计算过程,想必大家都很熟悉,它是属于一种推迟求值的方式,不能够立即求出最终结果,每���次的计算都依赖于它的子计算状态,直到遇到边界之后,才逐层返回计算结果,最终返回到起点,求出结果。可以说,递归是从后向前推的过程,计算最后一步,也就是最终结果,也许就会需要倒数第一步的结果,那么就计算倒数第一步,以此类推,直到计算到第一步的时候,利用题设条件可以得到第一步的最终结果,然后再把计算结果逐层返回到起点,也就是计算最后一步的位置,得到最终结果。(是深度优先搜索的道理)但是如果计算的次数太多,迭代的层数太深,留给过程的栈空间很有可能会溢出,造成爆栈。

那么,根据上面的时间和空间复杂度两个指标,我们将评判这个用递归计算阶乘的计算进程的好坏。(其实计算进程就是算法,有木有?!)

我们可以看到,如果n是6,那么一共要计算12次,如果n是5那么要计算10次,所以相应的时间复杂度大致可以估算为O(N)。从(factorial 6)递归到边界最低端的时候,一共进行了6次迭代,随着n的增加,递归的次数会随n线性增长,所需的内存空间也会线性增长。所以控件复杂度应为O(N)。

综上所述,整个递归计算进程已经评价完毕,准确的说,这是一个线性递归计算进程。

正向(线性迭代)

算法:n!就是从1开始一直累乘到n,最终的累乘结果等于n!

根据这个算法,写出一个过程如下

(define (factorial n) (fact-iter 1 1 n)) (define (fact-iter product counter max-count) (if ( counter max-count) product (fact-iter (* counter product) (+ counter 1) max-count)))

从上述的过程我们可以看出,它在定义的时候,也是调用了自身。计算(factorial 6)的进算进程图示如下:

计算机中的黑魔法:尾递归

根据图示可以看出,每一次的计算状态由三个变量来维持,而且每一个状态的计算和其他状态的计算是独立的,各自状态的计算结果不依赖于其他的状态。每一次状态的计算都是干脆利落的执行,下一个状态计算所需要的资源通过参数传递,上一个状态计算完成之后就没有任何用处了,它不必为前一个或者后一个状态来保存什么有用的信息,需要的信息都根据参数传递给新的状态了。而且,在不断计算的过程中,由于把所需要的值的信息一直作为参数传递,一旦得出了最终的结果,不用像递归一样,把计算结果返回给上一个状态,而是直接返回给一开始的调用者。过程的调用都是在栈空间来开辟内存的,根据以上计算特点,上一个状态计算结束之后可以立即销毁之前所用的栈空间,避免了栈溢出。

这样的计算进程,很容易就可以看出来,时间复杂度O(N),空间复杂度,因为每次计算只需要保存三个变量,它不随n的变化而变化,则为O(1),常量级。不仅仅是这个计算过程这样,计算机当中所有的计算的空间复杂度都应该在常数级别下完成,因为内存空间是有限的。

上述计算进程被称为迭代,如果单就这个实例来说,应该是线性迭代进程。

迭代和递归的区别

在迭代的进程中,每个状态计算所需的信息都保存在了几个变量中,通过参数传递过去,不需要额外的再开辟栈空间来保存一些隐含的信息。但是递归是需要的,还存在着栈溢出的危险。

在迭代的进程中,如果我们丢失了一些状态的话,可以从任意一个状态开始继续进行计算,因为计算所需要的信息都保存在几个变量中,即使只剩下中间的一个状态,我们也能够根据计算进程把所有丢失的状态全都算出来。但是如果是递归的话,如果某一个状态的信息丢失了,那么即使它的子状态算出了结果返回给它,因为丢失了一些必要的状态信息,使得计算进程是无法进行下去的。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/c6b66c1476bb1e840ad5167de46e6595.html