首页 > 其他分享 >C语言:函数递归

C语言:函数递归

时间:2024-08-25 19:23:17浏览次数:13  
标签:1234 10 函数 递归 int C语言 阶乘 Print

目录

一、递归

1.1 递归的思想

1.2 递归的限制

二、递归举例

2.1 举例1:求n的阶乘

 画图推演

2.2 举例2:顺序打印一个整数的每一位

画图推演

​编辑  三、递归和迭代


一、递归

     递归是学习C语言函数绕不开的⼀个话题,那什么是递归呢?递归其实是⼀种解决问题的方法,在C语言中,递归就是函数自己调用自己。

写⼀个史上最简单的C语言递归代码:

#include <stdio.h>
int main()
{
 printf("hehe\n");
 main();//main函数中⼜调⽤了main函数 
 return 0;
}

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

1.1 递归的思想

     把一个大型复杂问题层层转化为一个与原问题相似,但规模较小的子问题来求解,直到子问题不能再被拆分,递归就结束了,所以递归的思考方式就是把大事化小的过程。

    递归中的递就是递推的意思,归就是回归的意思,接下来慢慢来体会。

1.2 递归的限制

    递归在书写的时候,有2个必要条件:

• 递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。

• 每次递归调用之后越来越接近这个限制条件。

二、递归举例

2.1 举例1:求n的阶乘

一个正整数的阶乘是所有小于及等于该数的正整数的积,并且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);
}

 画图推演

2.2 举例2:顺序打印一个整数的每一位

输入⼀个整数m,打印这个按照顺序打印整数的每一位。

比如: 输入:1234 输出:1  2  3  4   输入:520 输出:5  2  0

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

 1234%10就能得到4,然后1234/10得到123,这就相当于去掉了4  然后继续对123%10,就得到了3,再除10去掉3,以此类推不断的 %10 和 /10 操作,直到1234的每⼀位都得到。但是这里有个问题就是得到的数字顺序是倒着的

 但是我们有了灵感,我们发现其实⼀个数字的最低位是最容易得到的,通过%10就能得到,那我们假设想写⼀个函数Print来打印n的每⼀位,如下表示:

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

以此类推下去,就有

Print(1234)
==>Print(123) + printf(4)
==>Print(12) + printf(3)
==>Print(1) + printf(2)
==>printf(1) 

直到被打印的数字变成⼀位数的时候,就不需要再拆分,递归结束。 那么代码完成也就比较清楚:  

void Print(int n)
{
 if(n>9)
 {
 Print(n/10);
 }
 printf("%d ", n%10);
}
int main()
{
 int m = 0;
 scanf("%d", &m);
Print(m);
 return 0;
}

    在这个解题的过程中,我们就是使用了大事化小的思路 把Print(1234) 打印1234每⼀位,拆解为首先Print(123)打印123的每⼀位,再打印得到的4  把Print(123) 打印123每⼀位,拆解为首先Print(12)打印12的每⼀位,再打印得到的3  直到Print打印的是⼀位数,直接打印就行。

画图推演

  三、递归和迭代

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

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

 Fact函数是可以产生正确的结果,但是在递归函数调用的过程中涉及⼀些运行时的开销。

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

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

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

 所以如果不想使用递归就得想其他的办法,通常就是迭代的方式(通常就是循环的方式)。

比如:计算n的阶乘,也是可以产生1~n的数字累计乘在⼀起的。

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

上述代码是能够完成任务,并且效率是比递归的方式更好的。

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

例子:求第n个斐波那契数

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

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

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

    当我们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;
}

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


     本篇关于函数递归的内容就到这里了,希望对各位有帮助,如果有错误欢迎指出。

标签:1234,10,函数,递归,int,C语言,阶乘,Print
From: https://blog.csdn.net/2401_86551514/article/details/141526248

相关文章

  • Python 字符串反转函数的实现与解析
    Python字符串反转函数的实现与解析在Python编程中,字符串是最常用的数据类型之一。反转字符串是一个常见的编程任务,通常用于数据处理、文本分析和算法练习。本文将详细介绍如何实现一个反转字符串的函数,探讨不同的方法,并分析它们的优缺点。一、字符串反转的基本概念字......
  • 56个JavaScript 实用工具函数助你提升开发效率!
    今天来看看JavaScript中的一些实用的工具函数,希望能帮助你提高开发效率!整理不易,如果觉得有用就点个关注鼓励一下吧!1.数字操作(1)生成指定范围随机数export const randomNum = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;(2)数字千分......
  • DWS(GAUSSDB)函数返回结果集(表)
    -----------建表------------droptableifexistsemployees;CREATETABLEemployees(employee_idNUMBER(10)PRIMARYKEY,--EmployeeID,primarykeyfirst_nameVARCHAR2(50),--Employee'sfirstnamelast_nameVARCHAR2(50),--Employee'slastna......
  • Linux中的exec族函数
    exec系列函数用于替换当前进程的用户空间代码和数据,从而执行一个新的程序。调用exec系列函数不会创建新的进程,但会用新程序的代码和数据替换当前进程,因此调用exec后,进程的ID保持不变,但进程的行为变为执行新的程序exec系列函数有六个,分别是:execlintexecl(constcha......
  • [vue3] vue3 setup函数
    从语法上看,CompositionAPI提供了一个setup启动函数作为逻辑组织的入口,提供了响应式API,提供了生命周期函数以及依赖注入的接口,通过调用函数来声明一个组件。OptionsAPI选项式API在props、data、methods、computed等选项中定义变量;在组件初始化阶段,Vue.js内部处理这......
  • 反汇编和汇编的区别 怎么用汇编让C语言更小
    在计算机编程的世界中,反汇编和汇编这两个概念往往令人感到深奥而神秘。究竟反汇编和汇编之间有何异同?这是程序员们经常探讨的话题。汇编语言作为一种底层编程语言,与计算机硬件密切相关,而反汇编则是将机器码还原为可读的汇编语言的过程。本文将深入研究反汇编和汇编的区别,帮助......
  • C++函数调用栈从何而来
    竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生~个人主页:rainInSunny | 个人专栏:C++那些事儿、Qt那些事儿目录写在前面原理综述x86架构函数调用栈分析如何获取rbp寄存器的值总结写在前面  程序员对函数调用栈是再熟悉不过了,无论是使用IDE调试还是GDB等工具进行调试,都离......
  • 最全!万字长文总结opencv-python常用函数(一)
    文章目录一,简介:二,图像的基础操作:2.1,图像的读取显示与保存2.1.1图像的读取cv2.imread:2.1.2图像的显示cv2.imshow与等待cv2.waitKey:2.1.3图像保存cv2.imwrite:2.2,图像属性获取:2.3,图像裁剪cv2.selectROI:2.4,图像通道的拆分cv2.split:2.5,图像通道的合并cv2.merge:三,图像的数值......
  • C++函数调用栈从何而来
    竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生~个人主页:rainInSunny | 个人专栏:C++那些事儿、Qt那些事儿文章目录写在前面原理综述x86架构函数调用栈分析如何获取rbp寄存器的值总结写在前面  程序员对函数调用栈是再熟悉不过了,无论是使用IDE调试还是GDB......
  • C语言函数介绍(上)
    函数概念库函数标准库和头文件库函数的使用方法头文件包含库函数文档的一般格式自定义函数函数的语法形式函数例子形参和实参实参形参实参和形参的关系return语句数组做函数参数函数概念数学中我们其实就见过函数的概念,比如:一次函数y=kx+b,k和b都是常数,给⼀个......