初阶C语言:函数递归

简介: C语言知识点函数递归的详细介绍,让你轻松上手函数递归,递归虽好,可不要一直用递归喔!
函数递归是一种解决问题的好办法,但是能想到函数递归的解决方法也并不是轻而易举,还是得多思考思考,搞清楚函数递归的基本原理以及技巧就可以很好的使用递归来解决问题

紫蓝色几何渐变科技互联网微信公众号封面 (1).gif

1.什么是递归

程序调用自身的编程技巧称为递归( recursion)。 递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接 调用自身的 一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解, 递归策略 只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。 递归的主要思考方式在于:把大事化小 简单的说递归就是在一个函数中再调用这个函数

2.递归的两个必要条件

1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。 2. 每次递归调用之后越来越接近这个限制条。

递归最常见的的错误就是死递归,举一个很简单的例子:

#include <stdio.h>intmain()
{
printf("hehe\n");
main();   //会导致栈溢出return0;
}

在讲static关键字时提到过栈区,和静态区,今天再来看一下

2.1小练习1

接受一个整型值(无符号),按照顺序打印它的每一位。 例如: 输入:1234,输出 1 2 3 4

假设要实现上述题目的代码,按照现在所学的C语言知识看似只能用函数递归,那该怎么用呢?给大家用代码演示一下,看不懂也不要紧,我也会给大家剖析函数递归的代码

代码演示:

#include <stdio.h>voidprint(intnum)
{
if (num>9)
    {
print(num/10);
    }
printf("%d ", num%10);
}
intmain()
{
intnum=0;
scanf("%d", &num);
print(num);
return0;
}

这段代码现在看不懂也没有关系,我们来分析一下,以输入123为例

代码进入 print 函数之后,进行判断:这时的num为123,123>9所以进入的条件语句,进入条件语句之后,执行print(num/10),这时还来不及执行下面的 printf 函数,然后再次进入 print 函数 进入 print 函数之后,这时的num为12,12>9所以又会进入条件语句,然后再次调用 print 函数,这时同样也没有机会执行下面的打印函数,然后再次进入 print 函数 进入print函数之后,这时的num为1,1<9因此不会满足条件,不会进入条件语句 这时代码再往下走遇到了打印函数 printf ,这时就要打印了,打印1%10,也就是打印1,这时③这个 print 函数就执行完了,这时屏幕上也就打印了1,然后代码就往回返,到了②的 print 函数,因为②的 print 函数还没有执行完,这时就要执行②中的 print 函数 返回上来时就要紧接着执行②中没有执行的代码了,这时就会打印num%10,也就是12%10也就是在屏幕上打印了2,然后这时②的 print 函数就执行完了,代码再往回返 代码返回到①的 print 函数就要执行它还没有执行完的代码,这时屏幕上就打印123%10,也就是3,这时全部的代码就都执行完了,屏幕上也就打印出了1 2 3 要注意,在代码执行到print(num/10)时就会再次调用这个函数,也就是说会进入一个新的print函数,这就表明这个条件语句还没有执行完,并不会执行条件语句下面的语句

2.2小练习2

编写函数不允许创建临时变量,使用函数递归求字符串的长度

我们知道有一个函数strlen专门用来求字符串的长度,那我们也来实现一下,首先得知道字符串的长度是统计\0之前的字符个数,因此不能将\0包含进去,还要求使用递归,这时就可以将求出的作为函数的返回值返回给主函数

代码演示:

#include <stdio.h>intmy_strlen(constchar*str)
{
if (*str!='\0')   //不能统计\0    {
return1+my_strlen(str+1);
    }
return0;
}
intmain()
{
charstr[] ="abc";
intlen=my_strlen(str);
printf("%d", len);
return0;
}

接下来对这段代码来进行一个解读

练习完这两个代码,应该对函数递归有了初步的认识了,函数递归的特点就是大事化小,可以看到使用函数递归写出的代码简洁

3.递归与迭代

C语言递归和迭代(循环)的相同之处在于,它们都是用来解决 重复性问题 的编程技术。 不同之处 在于,递归是一种分而治之的思想,它将一个复杂的问题分解成若干个规模较小的相同问题,而迭代则是一种重复执行某一操作的方法,它不断地重复执行某一操作,直到满足某一条件为止。

3.1练习

求n的阶乘 分别使用递归和迭代 1!  1 2!  2 * 1 3!  3 * 2 * 1 4!  4 * 3 * 2 * 1 ......
//递归#include <stdio.h>intfactorial(intn)
{
if (n<=1)
return1;  //1的阶乘就是1所以返回1elsereturnn*factorial(n-1);
}
intmain()
{
intn=0;
scanf("%d", &n);
intret=factorial(n);
printf("%d的阶乘为: %d", n, ret);
return0;
}

求第n个斐波那契数 (不考虑溢出)

要求斐波那契数首先得知道斐波那契数列是什么

1 1 2 3 5 8 13  21 34 55....

像这样的数列被叫做斐波那契数列

关于这个数列的规律就是从第三项开始,每一项都等于前两项之和。即:F(n)=F(n-1)+F(n-2),其中n>=3,F(1)=1,F(2)=1。

代码演示:

#include <stdio.h>intfib(intn)
{
if (n<=2)
    {
return1;
    }
elsereturnfib(n-1) +fib(n-2);
}
intmain()
{
intn=0;
scanf("%d", &n);
intret=fib(n);
printf("第%d个斐波那契数列是:%d", n, ret);
return0;
}

但是上述代码都有问题

  • 在使用factorial来计算10000的阶乘时(不考虑正确与否),程序会崩溃
  • 在使用fib来计算第50个斐波那契数列时会很慢

这是为什么呢?

因为fib在调用时进行了大量的重复运算,所以会导致在计算50个斐波那契数列时会非常的耗时间

我们可以看一下fib函数在调用时会重复计算多少次第3个斐波那契数列

代码演示:

#include <stdio.h>intcount=0;
intfib(intn)
{
//统计第三个斐波那契数列的重复个数if (n==3)
count++;
if (n<=2)
return1;
elsereturnfib(n-1) +fib(n-2);
}
intmain()
{
intn=0;
scanf("%d", &n);
intret=fib(n);
printf("第%d个斐波那契数列是:%d\n", n, ret);
printf("count = %d\n", count);
return0;
}

在计算第35个斐波那契数列时,就单纯的计算第3个斐波那契数列都整整重复计算了3524578次

那么该怎么改进上述两个代码呢?

可以使用非递归 (迭代) 的方法来实现

//n的阶乘//迭代n的阶乘#include <stdio.h>intfactorial(intn)
{
inti=0;
intnum=1;
for (i=1; i<=n; i++)
    {
num*=i;
    }
returnnum;
}
intmain()
{
intn=0;
scanf("%d", &n);
intret=factorial(n);
printf("%d的阶乘为: %d", n, ret);
return0;
}
//第n个斐波那契数列//迭代#include <stdio.h>intfib(intn)
{
inta=1;
intb=1;
intc=1;
while (n>=3)
    {
c=a+b;
a=b;
b=c;
n--;
    }
returnc;
}
intmain()
{
intn=0;
scanf("%d", &n);
intret=fib(n);
printf("第%d个斐波那契数列是:%d\n", n, ret);
return0;
}

Tip:

1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。 2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。 3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销 函数递归经典例题(自主研究) 青蛙跳台阶 汉诺塔


关于函数递归的相关知识就分享到这里,感谢大家支持!

目录
相关文章
|
1月前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
67 10
|
1月前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
52 9
|
1月前
|
存储 Unix Serverless
【C语言】常用函数汇总表
本文总结了C语言中常用的函数,涵盖输入/输出、字符串操作、内存管理、数学运算、时间处理、文件操作及布尔类型等多个方面。每类函数均以表格形式列出其功能和使用示例,便于快速查阅和学习。通过综合示例代码,展示了这些函数的实际应用,帮助读者更好地理解和掌握C语言的基本功能和标准库函数的使用方法。感谢阅读,希望对你有所帮助!
41 8
|
1月前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
50 6
|
1月前
|
存储 C语言
【C语言】输入/输出函数详解
在C语言中,输入/输出操作是通过标准库函数来实现的。这些函数分为两类:标准输入输出函数和文件输入输出函数。
271 6
|
1月前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
64 6
|
1月前
|
C语言 开发者
【C语言】断言函数 -《深入解析C语言调试利器 !》
断言(assert)是一种调试工具,用于在程序运行时检查某些条件是否成立。如果条件不成立,断言会触发错误,并通常会终止程序的执行。断言有助于在开发和测试阶段捕捉逻辑错误。
42 5
|
5月前
|
机器学习/深度学习 C语言
【C语言篇】递归详细介绍(基础概念习题及汉诺塔等进阶问题)
要保持最小的步数,每一次汉诺塔问题(无论是最初还是递归过程中的),如果此时初始柱盘子数为偶数,我们第一步是把最上面的盘子移动到中转柱,如果为奇数,我们第一步则是将其移动到目标柱。
117 0
【C语言篇】递归详细介绍(基础概念习题及汉诺塔等进阶问题)
|
8月前
|
C语言
C语言递归问题【青蛙跳台阶】和【汉诺塔】
C语言递归问题【青蛙跳台阶】和【汉诺塔】
|
C语言
【C语言】用函数递归的方法解决汉诺塔问题
【C语言】用函数递归的方法解决汉诺塔问题
85 0