什么是递归?
函数直接或间接调用自身的过程称为递归,而相应的函数称为递归函数。使用递归算法,可以很容易地解决某些问题。此类问题的示例包括河内塔(TOH),有序/预购/后继树遍历,图的DFS等。
递归类型:
递归主要有两种类型,具体取决于一个函数从自身内部调用自身还是一个以上的函数相互调用。第一个称为直接递归,另一个称为间接递归。因此,两种类型的递归是:
- 直接递归:可将其进一步分为四种类型:
- 尾递归:如果递归函数调用了自己并且该递归调用是该函数的最后一条语句,则称为尾递归。在该调用之后,递归函数执行任何操作。该函数必须在调用时进行处理或执行任何操作,并且在返回时不执行任何操作。
例子:
- 尾递归:如果递归函数调用了自己并且该递归调用是该函数的最后一条语句,则称为尾递归。在该调用之后,递归函数执行任何操作。该函数必须在调用时进行处理或执行任何操作,并且在返回时不执行任何操作。
C
// Code Showing Tail Recursion
#include
// Recursion function
void fun(int n)
{
if (n > 0) {
printf("%d ", n);
// Last statement in the function
fun(n - 1);
}
}
// Driver Code
int main()
{
int x = 3;
fun(x);
return 0;
}
Java
// Java code Showing Tail Recursion
class GFG {
// Recursion function
static void fun(int n)
{
if (n > 0)
{
System.out.print(n + " ");
// Last statement in the function
fun(n - 1);
}
}
// Driver Code
public static void main(String[] args)
{
int x = 3;
fun(x);
}
}
// This code is contributed by pratham76.
C#
// C# code Showing Tail Recursion
using System;
class GFG
{
// Recursion function
static void fun(int n)
{
if (n > 0)
{
Console.Write(n + " ");
// Last statement in the function
fun(n - 1);
}
}
// Driver Code
public static void Main(string[] args)
{
int x = 3;
fun(x);
}
}
// This code is contributed by rutvik_56
C
// Converting Tail Recursion into Loop
#include
void fun(int y)
{
while (y > 0) {
printf("%d ", y);
y--;
}
}
// Driver code
int main()
{
int x = 3;
fun(x);
return 0;
}
C
// C program showing Head Recursion
#include
// Recursive function
void fun(int n)
{
if (n > 0) {
// First statement in the function
fun(n - 1);
printf("%d ", n);
}
}
// Driver code
int main()
{
int x = 3;
fun(x);
return 0;
}
C
// Converting Head Recursion into Loop
#include
// Recursive function
void fun(int n)
{
int i = 1;
while (i <= n) {
printf("%d ", i);
i++;
}
}
// Driver code
int main()
{
int x = 3;
fun(x);
return 0;
}
C
// C program to show Tree Recursion
#include
// Recursive function
void fun(int n)
{
if (n > 0) {
printf("%d ", n);
// Calling once
fun(n - 1);
// Calling twice
fun(n - 1);
}
}
// Driver code
int main()
{
fun(3);
return 0;
}
C
// C program to show Nested Recursion
#include
int fun(int n)
{
if (n > 100)
return n - 10;
// A recursive function passing parameter
// as a recursive call or recursion
// inside the recursion
return fun(fun(n + 11));
}
// Driver code
int main()
{
int r;
r = fun(95);
printf("%d\n", r);
return 0;
}
C
// C program to show Indirect Recursion
#include
void funB(int n);
void funA(int n)
{
if (n > 0) {
printf("%d ", n);
// Fun(A) is calling fun(B)
funB(n - 1);
}
}
void funB(int n)
{
if (n > 1) {
printf("%d ", n);
// Fun(B) is calling fun(A)
funA(n / 2);
}
}
// Driver code
int main()
{
funA(20);
return 0;
}
输出:
3 2 1
- 让我们通过跟踪递归函数树来理解该示例。这就是调用的方式以及产生输出的方式。
- 尾递归的时间复杂度:O(n)
尾递归的空间复杂度:O(n)
注意:此特定示例给出了时间和空间复杂度。对于另一个示例,它可能会有所不同。
现在,让我们将“尾递归”转换为“循环”,并在时间和空间复杂度方面进行比较,并确定哪种效率更高。
C
// Converting Tail Recursion into Loop
#include
void fun(int y)
{
while (y > 0) {
printf("%d ", y);
y--;
}
}
// Driver code
int main()
{
int x = 3;
fun(x);
return 0;
}
输出:
3 2 1
- 时间复杂度:O(n)
空间复杂度:O(1)
注意:此特定示例给出了时间和空间复杂度。对于另一个示例,它可能会有所不同。
因此可以看出,在循环的情况下,空间复杂度为O(1),因此从空间复杂度方面考虑,用循环而不是尾部递归编写代码要好于尾部递归。
为什么在循环情况下空间复杂度降低了?
在解释这一点之前,我假设您熟悉在程序执行期间如何将数据存储在主存储器中的知识。总之,在程序执行时,主存储器分为三部分。第一部分为代码段,第二部分为代码段。一个是堆内存,另一个是堆栈内存。请记住,该程序只能直接访问堆栈内存,不能直接访问堆内存,因此我们需要指针的帮助才能访问堆内存。
现在让我们了解为什么在循环情况下空间复杂度降低了吗?
在循环执行函数“(void fun(int y))”的情况下,仅在堆栈存储器中创建了一个激活记录(仅针对“ y”变量创建了激活记录),因此它仅占用堆栈中的“一个”存储器单元,因此它的空间复杂度为O(1),但在每次调用递归函数时,每次调用都会在堆栈中创建一个单独的激活记录。因此,如果不存在“n”个调用,则它将占用堆栈中的“n”个内存单元所以它的空间复杂度是O(n)。 - Head递归:如果递归函数本身调用并且该递归调用是该函数的第一条语句,则称为Head递归。调用前没有声明,也没有任何操作。该函数不必处理或执行任何操作,并且所有操作都在返回时完成。
例子:
C
// C program showing Head Recursion
#include
// Recursive function
void fun(int n)
{
if (n > 0) {
// First statement in the function
fun(n - 1);
printf("%d ", n);
}
}
// Driver code
int main()
{
int x = 3;
fun(x);
return 0;
}
输出:
1 2 3
- 让我们通过跟踪递归函数树来理解该示例。这就是调用的方式以及产生输出的方式。
- 头部递归的时间复杂度:O(n)
头部递归的空间复杂度:O(n)
注意:此特定示例给出了时间和空间复杂度。对于另一个示例,它可能会有所不同。
注意: Head递归不能像Tail Recursion那样轻易地转换成循环,而是可以将上面的代码转换成循环。
C
// Converting Head Recursion into Loop
#include
// Recursive function
void fun(int n)
{
int i = 1;
while (i <= n) {
printf("%d ", i);
i++;
}
}
// Driver code
int main()
{
int x = 3;
fun(x);
return 0;
}
输出:
1 2 3
- 树递归:要了解树递归,首先让我们了解线性递归。如果递归函数调用了一次,则称为线性递归。否则,如果递归函数多次调用自身,则称为Tree Recursion 。
例子:
线性递归的伪代码
fun(n)
{
if(n>0)
{
// some code
fun(n-1); // Calling itself only once
{
//some code
}
- 树递归程序
C
// C program to show Tree Recursion
#include
// Recursive function
void fun(int n)
{
if (n > 0) {
printf("%d ", n);
// Calling once
fun(n - 1);
// Calling twice
fun(n - 1);
}
}
// Driver code
int main()
{
fun(3);
return 0;
}
输出:
3 2 1 1 2 1 1
- 让我们通过跟踪递归函数树来理解该示例。这就是调用的方式以及产生输出的方式。
- 树递归的时间复杂度:O(2 ^ n)
树递归的空间复杂度:O(n)
注意:此特定示例给出了时间和空间复杂度。对于另一个示例,它可能会有所不同。 - 嵌套递归:在此递归中,递归函数将作为递归调用传递参数。这意味着“递归内部递归”。让我们看示例以了解此递归。
例子:
C
// C program to show Nested Recursion
#include
int fun(int n)
{
if (n > 100)
return n - 10;
// A recursive function passing parameter
// as a recursive call or recursion
// inside the recursion
return fun(fun(n + 11));
}
// Driver code
int main()
{
int r;
r = fun(95);
printf("%d\n", r);
return 0;
}
输出:
91
- 让我们通过跟踪递归函数树来理解该示例。这就是调用的方式以及产生输出的方式。
- 间接递归:在这种递归中,可能有多个函数,并且它们以循环方式相互调用。
- 在上面的图中,fun(A)调用fun(B),fun(B)调用fun(C),fun(C)调用fun(A),因此形成一个循环。
例子:
C
// C program to show Indirect Recursion
#include
void funB(int n);
void funA(int n)
{
if (n > 0) {
printf("%d ", n);
// Fun(A) is calling fun(B)
funB(n - 1);
}
}
void funB(int n)
{
if (n > 1) {
printf("%d ", n);
// Fun(B) is calling fun(A)
funA(n / 2);
}
}
// Driver code
int main()
{
funA(20);
return 0;
}
输出:
20 19 9 8 4 3 1
- 让我们通过跟踪递归函数树来理解该示例。这就是调用的方式以及产生输出的方式。