递归与尾递归详解
作者:mobiledu2502906525 | 来源:互联网 | 2023-01-26 11:29
递归与尾递归详解下面两个程序是scheme(Lisp语言)写的计算阶乘的递归和尾递归实现线性递归:(define(factorialn)(if(n1)1(*n(factorial(-
递归与尾递归详解
下面两个程序是scheme(Lisp语言)写的计算阶乘的递归和尾递归实现
线性递归:
(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, 并放弃所有
局部变量压栈处理,就可以了。这样一来,堆栈根本就没有被占用,每次调用都是重
新使用调用者的堆栈。
尽管尾递归比递归高效,但并非所有的递归算法都可以转成尾递归的,因为尾递归本质
上执行的是迭代的计算过程。这与并非所有的递归算法都可以转成迭代算法的原因是一样的。
参考来源:《计算机程序的构造和解释》
推荐阅读
-
本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ...
[详细]
蜡笔小新 2024-12-27 13:55:14
-
本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ...
[详细]
蜡笔小新 2024-12-27 21:29:35
-
-
本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ...
[详细]
蜡笔小新 2024-12-27 10:18:13
-
本文探讨了 C++ 中普通数组和标准库类型 vector 的初始化方法。普通数组具有固定长度,而 vector 是一种可扩展的容器,允许动态调整大小。文章详细介绍了不同初始化方式及其应用场景,并提供了代码示例以加深理解。 ...
[详细]
蜡笔小新 2024-12-26 15:38:03
-
最新发布的 libsodium 1.0.15 版本带来了若干不兼容的变更,其中包括默认密码散列算法的更改和其他重要调整。 ...
[详细]
蜡笔小新 2024-12-26 11:03:58
-
本文探讨了线性Kalman滤波器(LKF)在不同自由度(2、4、7)的车辆悬架系统中进行主动控制的应用。通过详细的仿真分析,展示了LKF在提升悬架性能方面的潜力,并总结了调参过程中的关键要点。 ...
[详细]
蜡笔小新 2024-12-27 20:47:55
-
本文探讨了Hive中内部表和外部表的区别及其在HDFS上的路径映射,详细解释了两者的创建、加载及删除操作,并提供了查看表详细信息的方法。通过对比这两种表类型,帮助读者理解如何更好地管理和保护数据。 ...
[详细]
蜡笔小新 2024-12-27 20:21:48
-
1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ...
[详细]
蜡笔小新 2024-12-27 19:32:17
-
本文详细介绍了七种经典的排序算法及其性能分析。每种算法的平均、最坏和最好情况的时间复杂度、辅助空间需求以及稳定性都被列出,帮助读者全面了解这些排序方法的特点。 ...
[详细]
蜡笔小新 2024-12-27 19:25:14
-
本文介绍如何利用动态规划算法解决经典的0-1背包问题。通过具体实例和代码实现,详细解释了在给定容量的背包中选择若干物品以最大化总价值的过程。 ...
[详细]
蜡笔小新 2024-12-27 19:17:15
-
本文详细记录了在银河麒麟操作系统和龙芯架构上使用 Qt 5.15.2 进行项目打包时遇到的问题及解决方案,特别关注于 linuxdeployqt 工具的应用。 ...
[详细]
蜡笔小新 2024-12-26 10:54:04
-
当在 Android 应用中使用 NDK 时,可能会遇到 java.lang.UnsatisfiedLinkError: Native method not found 的错误。本文将详细探讨该错误的原因及解决方案。 ...
[详细]
蜡笔小新 2024-12-26 09:33:25
-
本文探讨了并发编程中的关键设计原则,特别是Java内存模型(JMM)的happens-before规则及其对多线程编程的影响。文章详细介绍了DCL双重检查锁定模式的问题及解决方案,并总结了不同处理器和内存模型之间的关系,旨在为程序员提供更深入的理解和最佳实践。 ...
[详细]
蜡笔小新 2024-12-26 01:14:06
-
本文详细介绍了MicroATX(也称Mini ATX)和MATX主板规格,探讨了它们的结构特点、应用场景及对电脑系统成本和性能的影响。同时,文章还涵盖了相关操作系统的实用技巧,如蓝牙设备图标删除、磁盘管理等。 ...
[详细]
蜡笔小新 2024-12-25 18:53:29
-
在过去两周中,我们利用 ReportViewer 开发了与生产良率相关的报表,其中每个制程的直通率是所有测试项良率的乘积。由于 ReportViewer 没有内置的累乘函数,因此需要借助自定义代码来实现这一功能。本文将详细介绍实现步骤和相关代码。 ...
[详细]
蜡笔小新 2024-12-25 17:12:03
-
mobiledu2502906525
这个家伙很懒,什么也没留下!