当前位置: 首页 > news >正文

C——函数递归

在 C 语言里,函数递归是一种函数调用自身的编程技术。下面开始逐一介绍。

一、什么是递归?

递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。

写⼀个史上最简单的C语⾔递归代码:
#include <stdio.h>
int main()
{
    printf("hehe\n");
    main();//main函数中⼜调⽤了main函数
    return 0;
}
上述就是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问
题,代码最终也会陷⼊死递归,导致栈溢出(Stack overflow)。
1.递归的思想:
把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较⼩的⼦问题来求解;直到⼦问题不能再被拆分,递归就结束了。所以递归的思考⽅式就是把⼤事化⼩的过程。
递归中的递就是递推的意思,归就是回归的意思。
2.递归的限制条件
递归在书写的时候,有2个必要条件:
递归存在限制条件,当满⾜这个限制条件的时候,递归便不再继续。
每次递归调⽤之后越来越接近这个限制条件。
二、递归举例
1.求n的阶乘
⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。
⾃然数n的阶乘写作 n!。
题⽬:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
1.1代码的分析和实现
n的阶乘的公式: n! = n ∗ (n − 1)!
举例:
5!= 5 * 4 * 3 * 2 * 1
4!= 4 * 3 * 2 * 1
所以:5!= 5 * 4!
从这个公式不难看出:如何把⼀个较⼤的问题,转换为⼀个与原问题相似,但规模较⼩的问题来求解的。
n的阶乘和n-1的阶乘是相似的问题,但是规模要少了n。有⼀种有特殊情况是:当 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 ret = Fact(n);
    printf("%d\n", ret);
     return 0;
}
结果:
(这里不考虑n太大的情况,n太大容易溢出)
2.画图演练
2.1举例
顺序打印⼀个整数的每⼀位
输⼊⼀个整数m,按照顺序打印整数的每⼀位。
⽐如:
输⼊:1234 输出:1 2 3 4
输⼊:125 输出:1 2 5
2.2代码的分析和实现
拿到这个题目,我们需要想的第一件事就是如何得到每一位数。
如果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打印的是⼀位数,直接打印就⾏。
2.3画图演练
以1234每一位的打印来推演一下

三、递归与迭代

递归是⼀种很好的编程技巧,但是和很多技巧⼀样,也是可能被误⽤的,就像举例1⼀样,看到推导的公式,很容易就被写成递归的形式:
int Fact(int n)
{
    if (n == 0)
        return 1;
    else
        return n * Fact(n - 1);
}
Fact函数是可以产⽣正确的结果,但是在递归函数调⽤的过程中涉及⼀些运⾏时的开销。
在C语⾔中每⼀次函数调⽤,都需要为本次函数调⽤在内存的栈区,申请⼀块内存空间来保存函数调⽤期间的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。
函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。
所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出的问题。
所以如果不想使⽤递归,就得想其他的办法,通常就是迭代的⽅式(通常就是循环的⽅式)。
⽐如:计算 n 的阶乘,也是可以产⽣1~n的数字累计乘在⼀起的。
1.举例
求第n个斐波那契数
以计算 factorial(5) 为例,执行过程如下:
factorial(5) 调用 5 * factorial(4)。
factorial(4) 调用 4 * factorial(3)。
factorial(3) 调用 3 * factorial(2)。
factorial(2) 调用 2 * factorial(1)。
factorial(1) 满足基本情况,返回 1。
然后逐步回溯计算:
factorial(2) 返回 2 * 1 = 2。
factorial(3) 返回 3 * 2 = 6。
factorial(4) 返回 4 * 6 = 24。
factorial(5) 返回 5 * 24 = 120。
由此可以看出,这里不适合使用递归,用迭代的方式效率更高。
递归的优缺点
优点:
代码简洁:递归可以让代码更加简洁、易读,尤其是处理一些具有递归结构的问题,如树的遍历、分治算法等。
易于理解:对于某些问题,递归的思路更贴合人类的思维方式,更容易设计和实现。
缺点:
性能问题:递归调用会频繁地进行函数调用和返回操作,带来额外的开销,可能会导致性能下降。
栈溢出风险:如果递归深度过大,会导致栈空间耗尽,引发栈溢出错误。
注意事项
要确保递归函数包含基本情况,防止无限递归。
注意递归深度,避免栈溢出。在必要时,可以考虑使用迭代(循环)来替代递归。
迭代是指基于一个初始值,按照特定的规则反复执行一系列操作,每一次执行都会对前一次的结果进行更新,直到满足某个终止条件为止。这个过程可以看作是对一个状态不断进行修改和演进的循环过程。
递归和迭代对比
执行效率:
迭代:通常情况下,迭代的执行效率相对较高,因为它避免了函数调用栈的频繁操作,只是在循环结构内进行简单的指令重复执行,减少了额外的开销。
递归:递归由于不断地进行函数调用和返回,会有一定的时间和空间开销,尤其是在递归深度较大时,可能会出现栈溢出等性能问题。
代码可读性:
迭代:对于一些简单逻辑,迭代代码的结构清晰,易于理解和调试,直接通过循环条件和循环体就能明白代码的执行流程。
递归:递归代码在处理具有递归结构的问题(如树的遍历、分治算法等)时,代码可能更简洁,更符合人们对问题的分解式思考方式,但对于初学者或者复杂逻辑,理解起来可能有一定难度。

迭代和递归各有优缺点,在实际编程中,需要根据问题的特点、性能要求和代码的可读性来选择合适的方法。有时候,迭代和递归可以相互转换,通过合理的设计和优化,可以提高代码的性能和可维护性。

相关文章:

  • Vue 项目中二维码生成功能全解析
  • 数智管理学(八)
  • 今日行情明日机会——20250507
  • MySQL 联合查询的使用教程
  • 【C/C++】ARM处理器对齐_伪共享问题
  • 【多种不同提交方式】通过springboot实现与前端网页数据交互(非常简洁快速)
  • 计算机硬件(南桥):主板芯片组FCH和PCH的区别
  • 【渗透测试】命令执行漏洞的原理、利用方式、防范措施
  • draw.io流程图使用笔记
  • 蓝桥杯青少 图形化编程——“星星”点灯
  • MySQL数据库高可用(MHA)详细方案与部署教程
  • hadoop中的序列化和反序列化(3)
  • C# WPF 颜色拾取器
  • AI与情感计算:如何让机器更好地理解人类情感与情绪?
  • CATIA高效工作指南——零件建模篇(二)
  • docker host模式问题
  • 二叉树与优先级队列
  • android中背压问题面试题及高质量回答范例
  • 怎么有效管理项目路径(避免使用绝对路径)
  • AI应用开发实战分享
  • 汪明荃,今生不负你
  • 创新创业50人论坛开幕在即,双创青年为何选择来上海筑梦?
  • 巴基斯坦宣布禁止印度船只入港
  • 重庆渝中警方:男子点燃摩托车欲寻衅滋事,被民警和群众合力制服
  • 国家能源局:鼓励各地探索深远海、沙戈荒等可再生能源制氢场景
  • 徐丹任武汉大学药学院院长:研究领域在国际上处领跑地位