递归
递归是一项非常重要的编程技巧,它使函数调用其本身。 示例之一就是阶乘的计算。 0 的阶乘明确地定义为 1。 n(大于 0 的整数)的阶乘是 1 到 n 之间所有整数的乘积。
使用递归
以下段落是用文字定义的一个阶乘计算函数。
如果数字小于零,则将其拒绝。 如果数字不是整数,则将其拒绝。 如果数字为零,其阶乘则为一。 如果数字大于零,则将其乘以下一个更小数字的阶乘。
若要计算任一个大于零的数字的阶乘,必须至少计算另外一个数字的阶乘。 函数在对当前数字执行计算之前,必须先对小于当前数字的相邻数字调用其自身。 这就是递归的示例。
递归和迭代(循环)密切相关,即函数可以使用递归或迭代返回相同的结果。 通常,某个计算适用于一种技巧或另一种技巧,您只须选择最自然或最理想的方法。
虽然递归的用处很大,但是如果使用不慎,创建的递归函数就可能从不返回结果并且不能到达终点。 这种递归导致计算机执行“无限”循环。 下面是一个示例:忽略阶乘计算文字描述中的第一项规则(有关负数的规则),然后计算任意负数的阶乘。 此计算将会失败,究其原因,举例来说,若要计算 -24 的阶乘,必须计算 -25 的阶乘。 若要计算 -25 的阶乘,必须先计算 -26 的阶乘,依此类推。 显然,这种计算永远得不出结果。
递归可能出现的另一个问题是:递归函数可能用尽所有可用的资源(如系统内存、堆栈空间等等)。 每次递归函数调用自身(或调用另一个函数,而另一个函数又调用原来的函数),递归函数就会占用一些资源。 当递归函数退出时,就会释放这些资源,但是函数的递归层次过多,就会用尽所有可用的资源。 发生这种情况时,就会引发异常。
因此,谨慎设计递归函数是非常重要的。 如果怀疑可能出现递归过多(或无限递归)的情况,则设计函数时就应加入计算函数调用其自身的次数的功能,并设置调用次数限制。 如果函数调用自身的次数超过阈值,则函数可以自动退出。 迭代的最大次数的最佳取值取决于递归函数。
下面又是一个阶乘函数,这一次是用 JScript 代码编写的。 还使用了类型批注,使函数只接受整数。 如果传递的数字无效(即小于零的数),则 throw 语句就会产生一个错误。 否则,使用递归函数计算阶乘。 递归函数采用两个参数,一个用作阶乘参数,另一个用作跟踪当前递归层次的计数器。 如果计数器没有达到最大递归层次,则返回原始数字的阶乘。
function factorialWork(aNumber : int, recursNumber : int ) : double {
// recursNumber keeps track of the number of iterations so far.
if (aNumber == 0) { // If the number is 0, its factorial is 1.
return 1.;
} else {
if(recursNumber > 100) {
throw("Too many levels of recursion.");
} else { // Otherwise, recurse again.
return (aNumber * factorialWork(aNumber - 1, recursNumber + 1));
}
}
}
function factorial(aNumber : int) : double {
// Use type annotation to only accept numbers coercible to integers.
// double is used for the return type to allow very large numbers to be returned.
if(aNumber < 0) {
throw("Cannot take the factorial of a negative number.");
} else { // Call the recursive function.
return factorialWork(aNumber, 0);
}
}
// Call the factorial function for two values.
print(factorial(5));
print(factorial(80));
该程序的输出为:
120
7.156945704626378e+118