1.如果将递归的计算过程排列成一张图形展现,那么先伸展,再收缩的计算过程就是线性递归,线性递归很消耗内存,因为他需要解析器保存逐渐扩展开来的计算链条,并且不断吞噬栈内存空间。这就是常规意义上认为“递归”性能较低的一个重要原因
线性递归:
2.如果将递归的计算过程排列开来,得出来的图形是固定宽度,不会扩张,那么这就是一个尾递归。实现方式很简单,就是利用多余的变量去递归更新原始变量,并且递归调用的时候,值不会依赖于上一次调用的值。这样保证了每次固定数量的变量存储递归计算的时候需要的值,不需要扩展,这样能保证内存占用最小化。传统语言java等无法实现尾递归,取而代之的是用迭代for,while的循环来处理,其实迭代也就是scheme中尾递归的一种表示表示名称。
尾递归:
3.将递归的计算过程展开之后,呈现出一个树形结构的,就称之为树形递归。树形递归由于不断扩展,性能也是很差的,因为他的计算步骤通常正比于树的节点数量(不断变多),而其空间消耗通常正比于树的深度(不断加深)。例如,斐波那契数列的计算,f(n)=f(n-1)+f(n-2),这样的计算公式,就会产生树形递归。
树形递归:
树形递归通过一些小技巧的转化,也能转化成线性迭代过程,从而拥有较少的空间消耗,但是更多情况下树形递归是一种非常直观简便的描述程序递归过程的一种方式。
例如,找零钱算法:有半美元,1/4美元,10美分,5美分,1美分的硬币,同时你手上有1美元需要找零钱,问,使用这些硬币有多少种找零钱的方式。如果你手里需要找零的不是1美元,而是某个不确定的数目,那又有多少种找零方式。
利用树形递归的思路,很容易分析出找零规律:
找零方式=完全不使用某种硬币时的找零方式+使用这种硬币的找零方式
而
使用这种硬币的找零方式=(找零钱数-该货币面值)的额度使用所有钱币找零方式
4.使用递归计算过程很容易,因为直接根据思路,套出代码就行,例如求b的n次方,首先列出公式:
n=0,b=1;
否则,b=b^(n-1)* b;
很容易套出代码
(define (expt b n) (if (= n 0) 1 ;如果等于n=0,直接返回1 (* b (expt b (- n 1))))) ;否则按照公式进行递归计算
如果需要改进他的空间效率,那么需要
(define (exp b n) (exp-iter b n 1))(define (exp-iter b n y) ;用y存储计算过程中保留的数据 (if (= n 0) y ;如果n=0,直接返回y (exp-iter b (- n 1) (* y b)))) ;按照公式求职
注意的是,用来保留计算过程中生成的值的新引入的变量y与b的关系必须保持y*(b^n)不变。
如:b^(n-1))*y*b依然等于(b^n)*y。
之所以这样的原因很简单,当传递了新的b和新的n给exp-iter方法中的参数时,原始的b和n都会改变,也就是新的b^n的值会出现改变,而另外的一个y正是存储了这部分改变的值,使得他们的乘积依然等于原始b^n的值不变。
这也是习题1.16中解题能否成功的关键步骤。
5.正则序和应用序
先对各个运算符和运算对象进行求值,然后将得到的过程应用于得到的实际参数,这就是解释器的应用序解析
先不求出运算对象的值,直接先展开,等到实际需要的时候再进行求值,这就是解释器的正则序解析