Data Structures Algorithms 简明教程

Recursion Algorithms

Recursion

一些计算机编程语言允许模块或函数调用自身。这种技术称为递归。在递归中,一个函数 α 要么直接调用自身,要么调用一个函数 β 而该函数反过来又调用了原来的函数 α 。函数 α 称为递归函数。

Example − 一个调用自身的函数。

int function(int value) {
   if(value < 1)
      return;
   function(value - 1);

   printf("%d ",value);
}

Example − 一个调用另一个函数(而该函数反过来又调用自身)的函数。

int function1(int value1) {
   if(value1 < 1)
      return;
   function2(value1 - 1);
   printf("%d ",value1);
}
int function2(int value2) {
   function1(value2);
}

Properties

一个递归函数可以像循环一样无限运行。为了避免递归函数无限运行,递归函数必须具有以下两个特性:

  1. Base criteria − 必须至少有一个基本标准或条件,即满足此条件时,函数停止递归调用自身。

  2. Progressive approach − 递归调用应该逐步进行,即每次进行递归调用时,它都更接近基本标准。

Implementation

许多编程语言通过 stacks 实现递归。通常,每当一个函数 ( caller ) 调用另一个函数 ( callee ) 或自身作为被调用方时,调用方函数会将执行控制权转移到被调用方。这个转移过程还可能涉及将一些数据从调用方传递到被调用方。

这意味着,调用方函数必须暂时挂起其执行,并在执行控制权从被调用方函数返回时继续执行。在这里,调用方函数需要从它暂停执行时的执行点准确地开始。它还需要它当时正在处理的确切相同的数据值。为此,将会为调用方函数创建一个激活记录(或堆栈帧)。

activation records

这个激活记录保存有关局部变量、形式参数、返回地址和传递给调用方函数的所有信息。

Analysis of Recursion

有人可能会争论为何使用递归,因为相同的任务可以用迭代来完成。第一个原因是,递归使程序更具有可读性,并且由于采用了最新的增强型 CPU 系统,因此递归比迭代更有效。

Time Complexity

对于迭代,我们采用迭代次数来计算时间复杂度。同样,对于递归,假设一切都是常数,我们尝试找出进行递归调用的次数。对函数进行一次调用为 Ο(1),因此递归调用 (n) 次将使递归函数 Ο(n)。

Space Complexity

空间复杂度是指模块执行所需的额外空间量。对于迭代,编译器几乎不需要任何额外空间。编译器不断更新在迭代中使用的变量值。但是在递归的情况下,系统需要在进行每次递归调用时存储激活记录。因此,人们认为递归函数的空间复杂度可能高于具有迭代的函数。

Example

以下是各种编程语言中对递归的实现 −