首页 > 其他分享 >函数的递归调用(零基础理解递归)

函数的递归调用(零基础理解递归)

时间:2024-04-03 21:29:35浏览次数:10  
标签:调用 return 函数 递归 int printf 阶乘 Fact

目录


正文开始

一. 什么是递归

什么是递归?

递归是c语言学习中一个绕不开的话题, 那什么是递归呢? 递归其实就是一种解决问题的方法, 在c语言中, 递归就是函数自己调自己.
写一个史上最简单的C语言递归代码:

#include<stdio.h>

int main(){
printf("hehe\n");
main();//这里main函数又调用自己
return 0;
}

上述代码就是一个简单的递归程序, 只不过上面的递归只是为了演示递归的基本形式, 不是为了解决问题, 代码最终也会陷入死循环, 导致栈溢出 (Stack overflow).

在这里插入图片描述

二. 递归的限制条件

  1. 递归的思想:
    把一个大模型复杂问题层层转化为一个与原问题相似, 但规模较小的问题来求解, 直到子问题不能再被拆分, 所以递归的思考方式是把大问题化小的过程.
    递归中的递就是递推的意思, 归就是回归的意思, 接下来请读者来体会.
  2. 递归的限制条件:
    递归在书写的时候, 有两个必要条件:
  • 递归存在限制条件, 当满足这个限制条件的时候, 递归便不再继续.
  • 每次递归调用之后越来越接近这个限制条件.
    在下面的举例中, 我们会逐步体会到这两个限制条件

三. 递归的举例

  • 举例1: 求n的阶乘
    一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积, 并且0的阶乘为1.
    自然数n的阶乘写作n!

题目:计算n的阶乘(不考虑溢出), n的阶层就是1~n的数字积累相乘.

  • 分析和代码实现
    我们知道n的阶乘的公式: n! = n * (n-1) !
举例:
5!=5*4*3*2*1
4!=4*3*2*1
所以5!=5*4!

这样的思路就是把一个较大的问题, 转化成一个与原问题相似, 但规模较小的问题来求解的.
当n==0的时候,n的阶乘是1, 其余的n的阶乘都是可以通过公式计算.
n的阶乘的递归公式如下:

在这里插入图片描述
那我们就可以写出函数Fact求n的阶乘, 假设Fact(n)就是用来求n的阶乘, 那么Fact(n-1)就是求n-1的阶乘, 函数如下:

int Fact(int n){
	if(n==0)
		return 1;
	else
		return n*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 result = Fact(n);
	printf("%d\n", result);
	return 0;
}

运行结果:
在这里插入图片描述

画图推演:

在这里插入图片描述

  • 举例2: 顺序打印一个整数的每一位
    题目: 输入一个整数m, 按照顺序打印整数的每一位
    比如:
    输入:1234 输出:1 2 3 4
    输入:520 输出:5 2 0

分析和代码
这个题目, 放在我们面前, 首先想到的是, 怎么得到这个数的每一位呢?
如果n是一位的话, n的每一位就是n自己
n如果超过1位的话, 就拆分每一位

1234%10就能得到4, 然后1234/123, 这就相当于去掉了4, 以此类推, 不断的%10和/10的操作, 直到1234的每一位都得到; 但是这里有个问题就是得到的数字顺序是倒着的. 但是我们有了灵感, 我们发现其实一个数字的最低为是最容易得到的, 通过%10就得到, 那我们假设写一个函数Print来打印n的每一位,如下所示:

Print(n)
如果n是1234,那么表示
print(1234) 打印1234的每一位
其中1234中4可以通过%10得到,那么
print(1234)就可以拆分成为两步:
1.print(1234/10)
2.printf(1234%10)
完成上述2步,那就完成了1234每一位的打印
那么print(123)又可以拆分为printf(123/10)+printf(123%10)

以此类推下去, 就有

   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 > 10) {
		Print(n / 10);
	}
	printf("%d ", n % 10);
}

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

输入输出结果:
在这里插入图片描述

画图推演:
在这里插入图片描述

四. 递归与迭代

递归是一种很好的编程技巧, 但是和很多技巧一样, 也是可能被误用的, 就像举例1一样, 看到推导的公式, 很容易写出递归的形式:

在这里插入图片描述

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

Fact函数是可以产生正确的结果, 但是在递归函数调用的过程中涉及一些运行时的开销.
所以如果不想使用递归就想得到其它的方法, 通常就是迭代的方式(通常就是循环的方式).
比如:计算n的阶乘,也是可以产生1~n的数字累计乘在一起的

int Fact(int n) {
	int i = 0;
	int ret = 1;
	for (i = 0; i <= n; i++) {
		ret *= i;
	}
	return ret;
}

事实上,我们看到的许多问题是以递归的形式进⾏解释的,这只是因为它⽐⾮递归的形式更加清晰,
但是这些问题的迭代实现往往⽐递归实现效率更⾼。
当⼀个问题⾮常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。

举例3:求第n个斐波那契数
我们也能举出更加极端的例⼦,就像计算第n个斐波那契数,是不适合使⽤递归求解的,但是斐波那契
数的问题通过是使⽤递归的形式描述的,如下:

在这里插入图片描述

看到这公式,很容易诱导我们将代码写成递归的形式,如下所⽰:

int Fib(int n)
{
 if(n<=2)
 return 1;
 else
 return Fib(n-1)+Fib(n-2);
}

测试代码:

#include <stdio.h>
int main()
{
 int n = 0;
 scanf("%d", &n);
 int ret = Fib(n);
 printf("%d\n", ret); 
 return 0;
 }

当我们n输⼊为50的时候,需要很⻓时间才能算出结果,这个计算所花费的时间,是我们很难接受的,
这也说明递归的写法是⾮常低效的,那是为什么呢?
在这里插入图片描述
其实递归程序会不断的展开,在展开的过程中,我们很容易就能发现,在递归的过程中会有重复计
算,⽽且递归层次越深,冗余计算就会越多。我们可以作业测试:

#include <stdio.h>
int count = 0;
int Fib(int n)
{
 if(n == 3)
 count++;//统计第3个斐波那契数被计算的次数
 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); 
 printf("\ncount = %d\n", count);
 return 0;
}

在计算第40个斐波那契数的时候,使⽤递归⽅式,第3个斐波那契数就被重复计算了
39088169次,这些计算是⾮常冗余的。所以斐波那契数的计算,使⽤递归是⾮常不明智的,我们就得
想迭代的⽅式解决。

我们知道斐波那契数的前2个数都1,然后前2个数相加就是第3个数,那么我们从前往后,从⼩到⼤计
算就⾏了。
这样就有下⾯的代码:

int Fib(int n)
{
 int a = 1;
 int b = 1;
 int c = 1;
 while(n>2)
 {
 c = a+b;
 a = b;
 b = c;
 n--;
 }
 return c;
}

迭代的⽅式去实现这个代码,效率就要⾼出很多了。
有时候,递归虽好,但是也会引⼊⼀些问题,所以我们⼀定不要迷恋递归,适可⽽⽌就好。

标签:调用,return,函数,递归,int,printf,阶乘,Fact
From: https://blog.csdn.net/2201_75644377/article/details/137184825

相关文章

  • (C++)内联函数——<入门>
    概念:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。 查看方式:1.在release模式......
  • nextjs 的函数,参数,属性装饰器的使用
    //属性装饰器constdoc1:PropertyDecorator=(target:any,val:string|symbol)=>{console.log(target);console.log(val);val="覆盖";}//方法装饰器constdoc2:MethodDecorator=(target:any,val:string|symbol,desc:any)=>{cons......
  • Python有哪些常用函数?
    Python是一种功能丰富的编程语言,它提供了大量的内置函数和库函数。以下是一些常用的Python函数,它们涵盖了多个领域:基础数据类型函数:int():将一个数或字符串转换成整数。float():将一个数或字符串转换成浮点数。str():将对象转换为字符串。list():将可迭代对象转换......
  • C++中的虚函数和虚函数表
    在上面一篇博客中 https://www.cnblogs.com/wphl-27/p/18111083,提到了虚函数,纯虚函数这篇博客我想继续进一步来说一下虚函数和虚函数表在C++中,每一个含有虚函数的类,编译器都会为它啊做出一个虚函数表(通常叫做vtable),这个虚函数表里面的每个元素都是函数指针,每个元素(函数......
  • Python函数(一):函数的声明、调用以及参数的使用
    目录为何要用到函数函数的定义和调用函数的参数参数的传递参数的类型 return语句为何要用到函数通常我们编写一段代码是为了实现特定的功能,比如想得到一段数字序列中的最大值、最小值和平均值并输出一个字典,编写一段代码如下:num=[96,85,69,82,52,99,72]m......
  • 【进来一起刷Java题】Java中使用空对象引用调用静态方法的奇特现象 附题目+解析 | ((Te
    目录一、题目二、解析三、答案:一、题目有关下述Java代码描述正确的选项是____。答案直接点目录里的跳转。publicclassTestClass{  privatestaticvoidtestMethod(){    System.out.println("testMethod");  }  publicstaticvoidmain(Str......
  • 浮点数转定点数(自编函数)
    方法:1、计算b=a*2^F,其中F是变量的分数长度,b是用十进制表示的。2、将所得b的值四舍五入到最接近的整数值。3、将(2)所得的b从十进制转换为二进制表示,并命名为新变量c。4、现在,假设c,需要n位来表示二进制中b的值。另一方面,通过模拟得到了W和F的值。所以W的值应该是等于或大于n。如果为......
  • 2、excel的循环查找vloopup函数
    excel的循环查找vloopup函数1、基本语法vloopup(查找值,数据表,显示列,匹配方式)=vloopup(A1,H2:G5,1,0)匹配方式:0为精准匹配,1为模糊匹配2、实例1ABCDE2小米12小红=vloopup(D1,A2:B5,1,0)3小花134小红145小明15=vloopup(D1......
  • 欧拉函数
    一、性质求欧拉函数fromcollectionsimportCounter#证明:容斥原理#f(N)=N*(1-1/p1)*(1-1/p2)*...*(1-1/pn)#与N互质的数的个数:N-N/P1-N/P2-...-N/Pn+N/(p1p2)+...+...-...defcal_euler(x):ans=xcnt=Counter()i=......
  • Python语法学习三之函数
    一、简单函数定义和调用def函数名():代码#无参数,无返回值的函数defprintName():print"cehae"printName()#无参数,有返回值的函数defgetAge():return18printgetAge()#有参数,无返回值的函数defprintSex(sex):printsexpr......