首页 > 其他分享 >递归之美:C语言中的函数递归

递归之美:C语言中的函数递归

时间:2024-11-21 12:15:49浏览次数:3  
标签:1234 10 函数 递归 int 之美 C语言 Print

  在编程的世界中,函数递归是一个强大且优雅的概念,它允许一个函数自我调用以解决问题。这种自我调用的特性使得递归在解决某些问题时变得特别高效和直观。本文将深入探讨函数递归的概念、应用以及需要注意的事项。

一、递归是什么?

        递归是学习C语⾔函数绕不开的⼀个话题,那什么是递归呢?

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

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

#include <stdio.h>

int main()
{
     printf("hehe\n");
     main();//main函数中⼜调⽤了main函数 
     return 0;
}

        上述就是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问 题,代码最终也会陷⼊死递归,导致栈溢出(Stack overflow)。

一、(1)、递归的思想:

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

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

一、(2)、递归的限制条件

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

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

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

二、 递归举例

二、(1)、 举例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==0 的时候,n的阶乘是1,其余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;
}
二、(1)、2、画图推演

二、(2)、举例2:顺序打印⼀个整数的每⼀位

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

输⼊:1234 输出:1  2  3  4 

输⼊:520 输出:5  2  0 

二、(2)、1、 分析和代码实现

        这个题⽬,放在我们⾯前,⾸先想到的是,怎么得到这个数的每⼀位呢?

        如果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)、2、 画图推演

 三、递归与迭代

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

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

 return n*Fact(n-1);
}

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

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

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

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

         以如果不想使⽤递归就得想其他的办法,通常就是迭代的⽅式(通常就是循环的⽅式)。 ⽐如:计算n的阶乘,也是可以产⽣1~n的数字累计乘在⼀起的。

int Fact(int n)
{
     int i = 0;
     int ret = 1;
     for(i=1; 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
//102334155
//count = 39088169

        这⾥我们看到了,在计算第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/do_yo/article/details/143889325

相关文章

  • 用递归算法实现,数组长度为5且元素的随机数在2-32间不重复的值
    functiongenerateRandomArray(length,min,max){if(max-min+1<length){thrownewError("Rangeistoosmalltogenerateanarraywithoutduplicates.");}functionrecursiveHelper(arr){if(arr.length===length){......
  • 用递归算法实现,数组长度为5且元素的随机数在2-32间不重复的值
    functiongenerateRandomArray(length,min,max){if(max-min+1<length){thrownewError("Rangeistoosmalltogenerateanarraywithoutduplicates.");}functionrecursiveHelper(arr){if(arr.length===length){......
  • c语言中的鞍点问题(详细版)
    1、什么是鞍点什么是鞍点?    鞍点鞍点,就是类似马鞍一样规律的点,即在一个矩阵中是每一行的最大值并且是每一列的最小值。 (无论多大的矩阵,如果存在鞍点,则只有一个,否则不存在鞍点,至于为啥,建议谷歌)2、找出鞍点的思路找出鞍点的思路 需要我们利用枚举数组(ps:不是高深......
  • 【C语言的奥秘3】C语言中的控制语句第二弹
    一、循环语句1、while循环(1)、while循环的执行流程while循环是当条件成立时进入循环体,当条件不成立则结束,不在进入到循环当中去。值得注意的是,while循环在第一次执行时,会先判断循环条件是否为真。如果条件为真,则进入循环体执行语句;如果条件为假,则跳过循环体,直接执行循环后......
  • C语言常用语句总结
    一:常用函数1、putchar函数:putchar函数(字符输出函数):向终端输出一个字符。一般形式为:        putchar(c)  //   输出字符变量c的值。        ==    printf(“%c”,c)2、getchar函数getchar函数(字符输入函数):从终端输入一个字符。getchar函数没......
  • 5.C语言数组(上)
    文章目录一、数组的概念二、一维数组的创建和初始化2.1数组的创建2.2数组的初始化2.3数组的类型三、一维数组的使用3.1数组下标3.2数组元素的打印3.3数组的输入四、一维数组在内存中的存储五、sizeof计算数组元素的个数六、二维数组的创建6.1二维数组的概念6.2......
  • C语言编程常见问题总结
    1、返回值处理①被调函数执行结果对业务有影响,调用者没有处理返回值:可能导致空指针访问、缺少回退处理(资源泄露)②处理函数的返回值不准确:返回值数据类型被错误转换,返回值比较的目标不是函数的返回值系列 2、断言的使用①在断言中包含了非逻辑表达式②对程序运行中可能发......
  • 2个月搞定计算机二级C语言——真题(12)解析
    1.前言本篇我们讲解2个月搞定计算机二级C语言——真题122.程序填空题2.1题目要求2.2提供的代码#include<stdio.h>#defineN3intfun(int(*a)[N]){inti,j,m1,m2,row,colum;m1=m2=0;for(i=0;i<N;i++){j=N......
  • C语言:数组的学习
    1.什么是数组?数组是一组相同类型元素的集合。数组可以存储1个或多个数据。数组中存储的数据的类型是相同的。数组分为一维数组和多维数组。变量和数组都是容器,变量只能存储一个数据,数组可以存储多个。2.一维数组创建和初始化存放在数组中的数据叫做数组的元素。数组是自......
  • 递归函数(详细讲解版)
    递归函数就是在函数的定义中使用函数自身的方法。这种函数调用自身的方式可以将一个复杂的问题逐步简化为相同类型的较简单问题。 关键要素 1.终止条件 这是递归函数中最重要的部分。如果没有终止条件,函数会一直调用自身,导致栈溢出(程序运行时栈空间耗尽)。终止条件......