函数递归篇

本文详细介绍了函数递归的概念、递归的实例(如阶乘和顺序打印整数),以及递归与迭代的区别和效率比较。通过求阶乘和斐波那契数的递归与迭代解决方案,强调了迭代在处理大数问题上的优势,以及递归在某些特定场景中的价值。
摘要由CSDN通过智能技术生成

函数递归

1.递归是什么

  • 递归(Recursion)是编程中一种非常重要的概念,它指的是一个函数直接或间接地调用自身。递归在解决某些问题时非常有用,特别是那些可以分解为更小、更简单的子问题时。

1.1 “最简单的”递归程序

#include <stdio.h>
int main(){
	printf("hello world\n");
	main();//递归调用main函数(这是一个不推荐的做法,并且可能导致无限递归和栈溢出  )
	return 0;
}

在这里插入图片描述

  • 上述的代码是一个简单的递归程序,但是这里只是演示一下低随大的基本原理,代码可能存在死递归导致栈溢出(Stack overflow)的问题;

1.2 递归思想

  • 把一个大型的复杂问题层层转化为一个与原问题相似的,但是规模较小的子问题来解决,直到子问题不能再被拆分成更小的子问题为止递归就结束了。所以递归思想就是把大事化小的逻辑过程;
  • 递归这两个字可以分开来理解。递,递推。归,回归
1.2.1 递归的限条件
  • 递归存在限制条件,当满足这个限制条件时,递归便不再继续;
  • 保证每次递归调用后结果越来越接近这个限制条件;

2. 递归的举例

2.1 求n的阶乘

  • 求阶乘的题目要求:计算n的阶乘(不考虑溢出),n的阶乘是1 ~ n的数字乘积相乘;
2.1.1分析和实现
  • 什么时阶乘?阶乘(Factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。通常使用n!来表示n的阶乘。阶乘的计算公式为:

n! = n × (n - 1) × (n - 2) × … × 3 × 2 × 1 = n * (n - 1)!

例如:

5! = 5 × 4 × 3 × 2 × 1 = 120
4! = 4 × 3 × 2 × 1 = 24
3! = 3 × 2 × 1 = 6
2! = 2 × 1 = 2
1! = 1
0! = 1(按照数学定义,0的阶乘为1)
n的阶乘的递归公式如下:
在这里插入图片描述
所以我们就可以写出函数Fact求n的阶乘,假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求n-1的阶乘,函数如下:

int Fact(int n)
{
	if(n==0)
	{
		return 1;
	}
	else
	{
		return Fact(n-1);
	}

我们只需要在主函数中接收调用即可;

#include <stdio.h>
int Fact(int n)
{
	if (n == 0) {
		return 1;
	}
	else {
		return n * Fact(n - 1);
	}
}
int main() {
	int n = 0;
	scanf("%d", &n);
	int ret = Fact(n);
	printf("%d",ret);
	return 0;
}

在这里插入图片描述

  • 这里暂时不考虑n太大的情况,n太大会溢出,在后面的 迭代部分 会讲到此问题;
2.1.1.1递归-阶乘图解

在这里插入图片描述

2.2 顺序打印一个整数的每一位

  • 题目要求:输入一个整数,按照原本的顺序打印整数的每一位

比如:

输入:1314     输出:1 3 1 4
输入:520       输出:5 2 0
2.2.1 分析和实现

1.如果输入的n是一位数,则输出本身;如果输入的是大于1位的数字,那么就需要拆分;
2.如果输入的是1314,那么1314%10就能得到尾数4,然后1314/10就能得到131,再用131%10得到尾数1,然后又将131/10得到13,以此类推,可以去掉尾数然后继续输出下一位,但是这里有一个问题就是输出的数字顺序是倒序输出的

  • 我们发现数字的最低位是最容易得到的,直接通过n%10即可,所以我们可以写一个Print函数打印n的每一位
  • 我们打印每一位的步骤是:
    1.Print(1234/10)//打印123的每一位
    2.printf(1234%10)//打印4
  • Print(1234)
  • Print(123)=====>printf(4)
  • Print(12)====>printf(3)
  • Print(1)==>printf(2)
  • printf(1)
    直到被打印的数字变成一位数字的时候就无需继续拆分,到此递归结束;
#include <stdio.h>
void Print(int n)
{
	if (n > 9) {
		Print(n / 10);
	}
	printf("%d ",n%10);
	
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	Print(n);
	return 0;
}

在这里插入图片描述

总结:

  • Print(1314)打印每一位,拆解为先用Print(131)打印131的每一位,再打印4
  • Print(131)打印每一位,拆解为先用Print(13)打印13的每一位,然后打印1
  • 以此类推,直到打印的是一个数字;
2.2.1.1 递归-推演图解

在这里插入图片描述

3.递归与迭代

迭代:通过循环结构(如for、while等)来重复执行一段代码,直到满足某个条件时停止。迭代不涉及到函数调用自身,而是通过更新循环变量来控制循环的次数。
迭代的优点在于它通常比递归更高效,因为它避免了递归调用带来的额外开销。此外,迭代在处理大量数据时通常更加安全,因为它不太可能导致栈溢出。然而,对于某些问题,递归可能更直观,更容易实现。

  • 刚刚阶乘的例子我们根据公式的推导,很容易想到使用递归要解决,就像刚刚的代码:
    不考虑栈溢出
    但是,Fact函数在对于一些较小的输入值时求阶乘,可以准确的求出结果,但是对于求一些较大的值的阶乘可能无法求出,因为递归函数调用的过程设涉及一些运行时的开销问题;

  • 在C语言中,每次函数调用都需要为本次函数调用在栈区申请一块内存空间,用来保存函数调用期间的各种局部变量的值。这块空间被称为运行时堆栈,或者函数栈帧

  • 如果函数不返回,函数对应的栈帧空间就会一直占用。因此,如果函数调用中存在递归调用,每一次递归函数调用都会开辟属于自己的栈帧空间。直到函数递归不再继续,开始回归,才逐层释放栈帧空间。

  • 因此,如果采用函数递归的方式完成代码,递归层次太深就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow)的问题。

所以我们刚刚计算阶乘的题目可以使用 迭代的方式 来进行:

#include <stdio.h>
int main() {
	int n = 0;
	scanf("%d", &n);
	int ret = 1;
	for (int j = 1; j <= n; j++)
	{
		ret *= j;
	}
	printf("%d",ret);
	return 0;
}
  • 用迭代的方式效率比递归更好;
  • 许多问题我们都以递归的形式来解释,这是因为它比递归的形式更加清晰,但是再这些问题上,迭代的实现往往会比递归的实现效率更高;
  • 所以一般来说,如果问题本身具有递归结构(如树形结构或分治算法),并且递归实现简单明了,那么递归可能是一个好的选择。然而,如果问题更适合通过循环结构来解决,或者递归实现可能导致性能问题,那么应该选择迭代。
  • 当复杂的问题难以用迭代是实现时,此时递归的简洁性便可以弥补它所带来的运行时的开销;

3.1 求斐波那契数

  • 我们说计算斐波那契数数不是适合用递归求解的,但是斐波那契数的问题可以通过递归的形式描述,让我们更好的理解:
    在这里插入图片描述
    看到这个示例图我们很容易想到递归:
#include <stdio.h>
int Fib(int n)
{
	if (n <= 2)
	{
		return 1;
	}
	else
	{
		return Fib(n - 1) + Fib(n - 2);
	}
}
int main()
{
	int n = 0;
	scanf("%d",&n);
	int ret = Fib(n);
	printf("%d\n",ret);
	return 0;
}

上面的代码,又是确实可以计算出正确结果,但是如果我们输入的值太大,就需要很长时间才能计算出结果,这说明递归的写法效率太低:
在这里插入图片描述
在这个递归程序中,它会不断展开,在展开过程中我们会发现有很多重复计算的过程,而且递归层次越多,重复计算的地方就越多。
我们可以做一个次数计算:

#include <stdio.h>
int count = 0;
int Fib(int n)
{
	
	if (n == 4) {
		count++;
	}
	if (n <= 2)
	{
		return 1;
	}
	else
	{
		return Fib(n - 1) + Fib(n - 2);
	}
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("%d\n", count);
	return 0;
}

在这里插入图片描述
我们可以看到在计算第30个斐波那契数的时候,第4个斐波那契数就被重复计算了196418,所以用递归来计算斐波那契数是非常不划算不明智的,故我们可以用迭代来实现
我么知道斐波那契数的前两个都是1,然后前2个数相加就是第3个数,以此类推:

#include <stdio.h>  
  
// 计算斐波那契数列的第n项的函数  
int Fib(int n)  
{  
    // 初始化变量a和b为斐波那契数列的前两项  
    int a = 1;  
    int b = 1;  
    // 初始化变量c为a和b的和,用于存储下一个斐波那契数  
    int c = 1;  
      
    // 当n大于2时执行循环,用于计算第n项斐波那契数  
    while (n > 2)  
    {  
        // 更新a和b的值  
        a = b;  
        b = c;  
        // 计算下一个斐波那契数,并存储在c中  
        c = a + b;  
        // 递减n的值,以便在下次循环中检查是否继续  
        n--;  
    }  
      
    // 返回计算得到的斐波那契数  
    return c;  
}  
  
int main()  
{  
    // 初始化变量n用于存储用户输入的数  
    int n = 0;  
    // 从标准输入读取用户输入的数  
    scanf("%d", &n);  
      
    // 调用Fib函数计算斐波那契数,并将结果存储在ret中  
    int ret = Fib(n);  
      
    // 打印计算得到的斐波那契数  
    printf("%d\n", ret);  
      
    // 程序正常结束,返回0  
    return 0;  
}

通过迭代的方式去实现效率会高很多

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值