再识C语言 DAY13 【递归函数(超详细)】

文章目录


前言

本文总结于此文章


一、函数递归

什么是递归

函数调用自身的编程技巧称为递归

(函数自己调用自己)

递归分为**递推和回归**

递归的策略

它通常把一个大型复杂的问题层层转换为一个与原问题相似的小问题来解决。

递归的重要思想:把大事化小

例如(史上最简单的递归):

c 复制代码
#include<stdio.h>
int main()
{
	printf("Hello World\n");
	main();
}

先一直打印 Hallo World,最终程序挂掉

递归的两个重要条件

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

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

练习一

接受一个整型值(无符号),按顺序打印他的每一位

例如:

输入:1234,输出 1 2 3 4

我们的第一想法就是:

c 复制代码
	1234%10 = 4
	1234/10=123
	123%10=3
	123/10=12
	12%10=2
	12/10=1
	1%10=1
	1/10=0

这样我们能得到数字的每一位,然后我们把他们打印出来

c 复制代码
#include<stdio.h>
#define _CRT_SECURE_NO_WARNINGS
int main()
{
  int a ,n;
	printf("请输入一个无符号的整型:");
	scanf("%d", &a);
while(a>10)
  {
  if(a>10)
	{
  n = a % 10;
  printf("\n%d  ",n);
  a = a / 10;
  }
}
    printf("\n%d",a);
	return 0;
}

我们发现输出结果和题目中的不同,这个问题需要我们用递归来进行

因为递归的原理是先递推再回归,一层一层的传递下去

我们定一个print函数,它的功能是按照顺序打印num的每一位。

打印1234的每一位:print(1234)

打印1234的每一位,我们可以先打印123的每一位再打印4

打印123的每一位:我们可以先打印12的每一位print(12)再打印3

打印12的每一位:我们可以先打印1的每一位print(1)再打印2

最后输出打印1 2 3 4

程序:

print(1234)

print(123) 4

print(12) 3 4

print(1) 2 3 4

最终输出:1 2 3 4

在写代码的过程中,我们只需要关注最后一层的递推和回归

我们的想法就是先把余的1打印出来,再把上一层余的2打出来,以此类推。

此时我们的n等于1 ,用来打印余1

c 复制代码
    printf("%d  ",n%10); 

因为这是我们上一层所调用的print函数,当这个函数执行完后,我们还要回到上一层,继续执行代码

此时n等于12,打印出来的结果为2。

c 复制代码
    printf("%d  ",n%10); 

依次循环,这就是我们的递归

首先,我们先把递归函数写出来

c 复制代码
  void print(int n)
  {
  
  if(n<10)
  {
    printf("%d  ",n);
  }
  else
  {
    print(n/10);
    printf("%d  ",n%10); 
  }
}

代码实现:

c 复制代码
#include <stdio.h>
  void print(int n)
  {
  
  if(n<10)
  {
    printf("%d  ",n);
  }
  else
  {
     print(n/10);
    printf("%d  ",n%10); 
  }
}
  int main ()
  {  
  int a=0;
  printf("输入一个数字\n");
  scanf("%d",&a);
  print(a);
  return 0;
}

当然也可以更简便:

其更简便的最根本的原因是限制条件不同,使用n>9时,就不用使用else

c 复制代码
#include<stdio.h>
void print(unsigned int n)
{
	if (n > 9)限制条件
	{
		print(n / 10);每次递归调用之后越来越接近这个限制条件
	}
	printf("%d ", n % 10);
}
int main()
{
	unsigned int num = 0;
	scanf("%u", &amp;num);
	print(num);
	return 0;
}

练习二

写一个可以求字符串长度的代码(老朋友了已经是)

我们有一个专门求字符串长度的库函数 strlen

长度等于开始到空字符之间的字符数(不包括空字符本身)\0

首先我们先创建一个字符数组

c 复制代码
char arr[] = "abc";

再用strlen函数

c 复制代码
#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = "abc";
	strlen(arr);
	printf("%d", strlen(arr));
	return 0;
}

我们自己创建一个函数,模仿写一个strlen函数

这里先用非递归方式

c 复制代码
#include<stdio.h>
#define _CRT_SECURE_NO_WARNINGS
int my_strlen(char* str)
{
    int count = 0;
    while (*str != '\0')
    {
        count++;
        str++;
    }
    return count;
}

int main(){
    char i[100];
    printf("请输入一个字符串: ");
    scanf("%s", i);
    my_strlen(i);
    printf(" %d", my_strlen(i));
    return 0;
}

count在这里只是起到一个计数器的作用

如果不太理解指针和数组,下面其辅助作用

int *p1 = &a;

int *p2 = &b;

int *p3 = &c;

int *p4 = &d;

arry[0] = &a;

arry[1] = &b;

arry[2] = &c;

arry[3] = &d;

下来试一下递归

如果第一个字符不是\0,那说明字符串里至少有一个字符
这就是我们的思路

****所以当第一个元素不是'\0'的话

求my_strlen("abc")就可以看成求1+my_strlen("bc")的长度

求1+my_strlen("bc")的长度就可以看成 求1+1+my_strlen("c")的长度

求1+1+my_strlen("c")的长度就可以看成 求1+1+1+my_strlen(" ")的长度,最后我们让my_strlen("\0 ")输出为零就行了

最后算出来等于3

首先写递归函数:

c 复制代码
int my_strlen(char* str)
{
    if(*str !='\0')
    {
        return 1 + my_strlen(str+1);
    }
    else
    {
        return 0;
    }
}

递归过程如下

代码如下

递归与迭代

练习三

求n的阶乘的和

先构思好递归(思路如下,是别的博主的图,博主文章在前言)

代码实现

c 复制代码
#include<stdio.h>
#define _CRT_SECURE_NO_WARNINGS
int my(int i)
{
    if(i<=1)
    {
        return 1;
    }
    else
    {
            return i *my(i -1);
    }
}

int main(){
    int i;
    printf("请输入一个数字: ");
    scanf("%d", &i);
    my(i);
    printf("%d", my(i));
    return 0;
}

但当数字变大时,递归会算不出结果

递归函数也只是一种解决问题的技巧,它和其它技巧一样,也存在某些缺陷,具体来说就是:递归函数的时间开销和内存开销都非常大,极端情况下会导致程序崩溃。

所以这道题最好用非递归的方式来计算,比如迭代,循环是一种迭代

练习四

求第N个斐波那契数列

什么是斐波那契数列

流程如下:

写出代码:

c 复制代码
#include<stdio.h>
#define  _CRT_SECURE_NO_WARNINGS
int Fib(int n)
{
	if(n<=2)
    {
        return 1;
    }
    else
    {
        return Fib(n-1)+Fib(n-2);
    }
}
int main()
{
	int n = 0;
	scanf("%d",&n);
	printf("%d",Fib(n));
	return 0;
}

在练习三、四中出现的问题

我们会出现这样的问题

-在使用 Fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。

-使用my函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。

原因是因
函数在调用过程中很多计算,其实一直在重复

系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出

如何解决这个问题

1. 将递归改写成非递归。
2. 使用static全局静态对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问


原文链接:https://blog.csdn.net/m0_68468727/article/details/126466506

c 复制代码
#include<stdio.h>
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;
}
 
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d\n", Fib(n));
	return 0;
}

提示

1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销


如果您发现文章有错误请与我留言,感谢

相关推荐
赵钰老师1 分钟前
基于R语言APSIM模型应用及批量模拟(精细农业、水肥管理、气候变化、粮食安全、土壤碳周转、环境影响、农业可持续性、农业生态等)
开发语言·数据分析·r语言
lly20240617 分钟前
Highcharts 饼图:数据可视化利器
开发语言
lw向北.23 分钟前
Qt For Android之环境搭建(Qt 5.12.11 Qt下载SDK的处理方案)
android·开发语言·qt
汤姆和佩琦42 分钟前
2024-12-25-sklearn学习(20)无监督学习-双聚类 料峭春风吹酒醒,微冷,山头斜照却相迎。
学习·聚类·sklearn
IT女孩儿1 小时前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
m0_748238921 小时前
webgis入门实战案例——智慧校园
开发语言·ios·swift
悲伤小伞1 小时前
C++_数据结构_详解二叉搜索树
c语言·数据结构·c++·笔记·算法
好学近乎知o1 小时前
正则表达式(学习Django过程中可能涉及的)
学习·正则表达式·django
雨中奔跑的小孩1 小时前
爬虫学习案例8
爬虫·学习
jieshenai1 小时前
使用 VSCode 学习与实践 LaTeX:从插件安装到排版技巧
ide·vscode·学习