下面两个程序是scheme写的计算阶乘的递归和尾递归实现
线性递归:
(define (factorial n)
(if (=n 1)
1
(* n (factorial (- n 1)))))
尾递归:
(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)))
用C写出来就是这样的:
线性递归:
long factorial(long n)
{
return(n == 1) ? 1 : n * factorial(n - 1);
}
尾递归:
long fact_iter(long product, long counter, long maxcount)
{
return (counter > maxcount) ? product : fact_iter(product*counter, counter+1, maxcount);
}
long factorial(long n)
{
return fact_iter(1, 1, n);
}
线性递归程序基于阶乘的递归定义,即:对于一个正整数n,n!就等于n乘以(n-1)!:
n! = n[(n-1)(n-2)…3*2*1] =n(n-1)!
程序一在计算4!时的过程是这样的:
(factorial 4)
(* 4 (factorial 3))
(* 4 (* 3 (factorial 2)))
(* 4 (* 3 (* 2 (factorial 1))))
(* 4 (* 3 (* 2 1)))
(* 4 (* 3 2))
(* 4 6)
24
而尾递归程序用的是另一种计算规则,即先用1乘以2,再将得到的结果乘以3,再乘以4,这样下
去直到n。程序中维持着一个变动中的乘积product,以及一个从1到n的计数器counter,这一
计算过程中的product和counter在每一步都按照下面规则改变:
product = counter * product
counter = counter + 1
这样循环下去,可以得到n!也就是计数器counter超过n时乘积product的值。
所以程序二在计算4!的过程是这样的:
(factorial 4)
(fact-iter 1 1 4)
(fact-iter 1 2 4)
(fact-iter 2 3 4)
(fact-iter 6 4 4)
(fact-iter 24 5 4)
24
一个对自己本身的递归尾调用,就叫做尾递归。这里尾调用的“尾”字,是指运行
时需要执行的最后一个动作。不是简单的语法字面上的最后一个语句。 尾递归实
际执行的是迭代的计算过程。
线性递归函数必须满足以下两个基本属性:
- 必须清晰无误地解决基的情况。
- 每一个递归的调用,必须包含更小的参数值。
而尾递归则不必满足这两个条件。
普通的线性递归比尾递归更加消耗资源, 在实现上说, 每次重复的过程调用都使得调
用链条不断加长. 系统不得不使用栈进行数据保存和恢复.而尾递归就不存在这样的问
题, 因为它的状态完全由函数的参数保存。并且,由于尾递归的函数调用出现在调用者
函数的尾部,因为是尾部,所以根本没有必要去保存任何局部变量,sp, pc的信息。
直接让被调用的函数返回时越过调用者,返回到调用者的调用者去。
尾调用优化不是什么很复杂的优化,实际上几乎所有的现代的高级语言编译器都支持
尾调用这个很基本的优化。 实现层面上,只需要把汇编代码call改成jmp, 并放弃所有
局部变量压栈处理,就可以了。这样一来,堆栈根本就没有被占用,每次调用都是重
新使用调用者的堆栈。
尽管尾递归比递归高效,但并非所有的递归算法都可以转成尾递归的,因为尾递归本质
上执行的是迭代的计算过程。这与并非所有的递归算法都可以转成迭代算法的原因是一样的。
分享到:
相关推荐
但Python标准解释器并未对尾递归进行优化,所以尾递归在Python中的效果并不明显。 3. **循环**: 循环是最有效的方法,如 `Fib_circle` 函数所示。通过循环,我们可以避免递归带来的额外开销,只需线性时间复杂度...
尾递归简介 尾递归是函数返回最后一个操作是递归调用,则该函数是尾递归。 递归是线性的比如factorial函数每一次调用都会创建一个新的栈(last-in-first-out)通过不断的压栈,来创建递归, 很容易导致栈的溢出。而尾...
6. **优化**:动画可能涵盖了尾递归的概念,以及编译器如何优化尾递归调用以避免栈空间的浪费。 通过这样的动画演示,学习者能够直观地理解栈的工作原理和递归的执行过程,这对于理解和解决复杂问题至关重要。在...
总的来说,C++递归线性搜索提供了一种简单直观的方式来查找数组中的特定数字,但可能不适合处理大数据。在实际编程中,我们需要根据问题的具体情况选择合适的算法,以实现最佳的性能和资源利用。理解递归的概念和其...
通过这份文档,我们可以看到,非线性数据结构的实现与应用是计算机科学领域的重要组成部分。了解并掌握这些基本概念、算法和操作,对于任何计算机科学专业在校大学生来说,都是基础而关键的技能。特别是对于那些有志...
线性递归是一种基本的递归形式,例如阶乘(factorial)和斐波那契数列(Fibonacci sequence)。阶乘的概念比较简单,唯一需要说明的是,0 的阶乘是 1 而非 0。斐波那契数列,又称黄金分割数列,指的是这样一个数列:...
递归优化主要包括尾递归优化和记忆化搜索(动态规划)。其中,记忆化搜索是一种有效减少重复计算的方法,它通过存储已计算过的子问题结果,避免了冗余的计算,显著提高了算法效率。以下是一种利用循环而非递归来实现...
1. **尾递归**:将递归转换为尾递归形式,减少重复计算。 2. **记忆化**:使用缓存机制来保存已经计算过的结果,避免重复计算。 3. **非递归实现**:对于某些递归算法,可以使用迭代等非递归的方式重新实现,以减少...
为避免这种情况,有时可以使用尾递归优化,或者采用迭代的方式替代递归。 总的来说,栈和递归是计算机科学中不可或缺的概念,理解并熟练掌握它们对于提升编程能力、解决复杂问题至关重要。无论是数据结构的实现、...
在编程领域,递归是一种强大的概念,它涉及函数或过程调用自身来解决问题。在C语言中,递归被广泛用于解决复杂问题,因为它能够简化代码结构并使其更...在实践中,结合递归与其他算法,可以编写出高效且优雅的代码。
1. **尾递归优化**:通过修改函数调用顺序,避免无限递归。 2. **辅助函数**:对于不能直接映射到递归函数的文法规则,可以使用辅助函数来辅助解析。 3. **LR(Look-Ahead)分析**:添加向前看的字符,帮助解决右...
在计算机科学中,递归与分治是两种重要的算法设计策略。它们在解决复杂问题时,能够将大问题分解为小问题,通过解决小问题来推导出大问题的解。接下来,我们将深入探讨这两种方法。 首先,递归是一种函数或程序调用...
在数组中,线性时间复杂度意味着算法的运行时间与输入数据的大小成正比。这通常优于其他如排序算法(如快速排序或归并排序),它们虽然在平均或最好情况下具有较低的时间复杂度,但在最坏情况下可能达到O(n log n)。...
总结来说,Python的递归算法涉及递归原理、线性递归和尾递归的概念,理解这些概念对于编写高效的递归程序至关重要。同时,要注意递归可能导致的栈空间限制,适时考虑优化策略,如尾递归优化,以提高程序性能。
线性结构是计算机科学中数据结构的一个重要概念,它是一种有序的数据集合,其中的元素按照特定的顺序排列,每个元素都有一个前驱和一个后继(在某些情况下,如链表的首元素或尾元素可能没有对应的前驱或后继)。线性...
空间复杂度可能与问题规模成线性关系(O(n)),尤其是在没有尾递归优化的情况下。 - **尾递归优化**:在某些情况下,如果递归调用是函数体的最后一个操作,编译器或解释器可以进行尾递归优化,将递归调用替换为跳转...
线性链表逆置是指将链表中的元素顺序反转,原本的头节点变成尾节点,原本的尾节点变成头节点。 在链表逆置的过程中,我们通常采用迭代或递归的方法。这里我们将主要讨论迭代方法,因为这种方法更易于理解和实现。 ...
1. 线性递归:每次递归调用只减少问题规模的一个单位,如上述的阶乘计算。 2. 尾递归:在递归调用的最后返回,没有其他操作。在某些语言中,尾递归可以被优化,避免额外的栈空间开销。 六、示例:递归打印数组 ```...