函数递归+青蛙跳台阶——“C”

news2024/9/22 3:38:08

各位CSDN的uu们你们好呀,今天小雅兰的内容终于要回到我们的C语言了,在之前,我写函数这篇博客的时候就讲过,会把函数递归的内容单独拿出来,然后呢当时是说下一篇博客就会更函数递归和青蛙跳台阶,由于一系列原因呢,我更了一段时间的高等数学的文章,把函数递归的内容一直放置到现在,我想着:做事情怎么也得有始有终吧,我怀着激动的心情,就有了今天的这篇文章啦


首先,我们先要知道一个问题,函数递归究竟是什么? 

 程序调用自身的编程技巧称为递归( recursion)。

 递归作为一种算法在程序设计语言中广泛应用。

 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解, 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

 递归的主要思考方式在于:把大事化小

函数递归的两个必要条件

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

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


下面,我们来看几个题目来更好地理解函数递归

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

思考:我们肯定是要封装一个函数来实现此功能。

   1234%10=4

   1234/10=123

   123%10=3

   123/10=12

   12%10=2

   12/10=1

   1%10=1

 如果采用这种方式,我们可以很容易地就输出4 3 2 1,但是题目要求我们输出1 2 3 4

 我们可以这样:Print(1234)

                          Print(123) 4

                          Print(12) 3 4

                          Print(1) 2 3 4

下面,我们尝试写一下代码

#include<stdio.h>
void Print(unsigned int n)
{
   if(n>9)//n是两位数以上的数字,才会拆分
   {
     Print(n/10);
   }
   printf("%d ",n%10);
}
int main()
{
   unsigned int num=0;
   scanf("%u",&num);
   Print(num);
   return 0;
}

 别看这段代码不长,但是想要很好地理解也是要花时间的

下面,我们来解释一下这段代码的意思:递归的意思是递推和回归

这段代码的意思我已经画图讲解地很清楚了,相信大家一看就明白

下面我们再来看一个题目

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

在做这道题目之前,我们首先得知道有这样一个库函数strlen,是专门求字符串长度的

strlen统计的是\0之前出现的字符的个数

所以在做这道题目之前,我们先来做另一个题目:编写函数,求字符串的长度(模拟实现strlen)

#include<stdio.h>
int my_strlen(char *str)
{
   int count=0;//计数器
   while(*str!='\0')
   {
      count++;
      str++;
   }
   return count;
}
int main()
{
   char arr[10]="abcdef";
   //[a b c d e f \0   ]
   int len=my_strlen(arr);//数组名表示首元素的地址
   printf("%d\n",len);
   return 0;
}

 这道题目我们是采用计数器的方法做的,下面我们再看看上面那道题目,采用递归的方式尝试一下

 

#include<stdio.h>
int my_strlen(char *str)
{
   if(*str!='\0')
   {
      return 1+my_strlen(str+1);//使用这种方式不会改变str
      //尽量不使用++str,因为这样会改变str
   }
   else 
   {
      return 0;
   }
}
int main()
{
   char arr[10]="abcdef";
   int len=my_strlen(arr);
   printf("%d\n",len);
   return 0;
}

 那么,为了更好地理解,我们还是采用画图的方式来看看

这个题目我们也就很好地讲清楚了


 递归与迭代

   求n的阶乘(不考虑溢出)

我们首先采用递归的方式来实现一下

#include<stdio.h>
int fac(int n)
{
   if(n<=1)
   {
     return 1;
   }
   else 
   {
     return n*fac(n-1);
   }
}
int main()
{
   int n=0;
   scanf("%d",&n);
   int ret=fac(n);
   printf("%d\n",ret);
   return 0;
}

 

 用递归的方式已经成功实现了,那么,我们来采用非递归的方式实现一下(也就是迭代)

#include<stdio.h>
int fac(int n)
{
   int i=0;
   int ret=1;
   for(i=1;i<=n;i++)
   {
      ret=ret*i;
   }
   return ret;
}
int main()
{
   int n=0;
   scanf("%d",&n);
   int ret=fac(n);
   printf("%d\n",ret);
   return 0;
}

 下面,我们再来看一个题目:求第n个斐波拉契数

 那首先,我们肯定要知道发斐波拉契数是个什么东西呀

  斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)在现代物理、准晶体结构、化学等领域,斐波那契数列都有直接的应用,为此,美国数学会从 1963 年起出版了以《斐波那契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。

 

 

 

 

 

 

 

 

 

 

 

 

哈哈哈,在学习C语言的过程中还可以学习数学,何乐而不为呢

 言归正传,我们来用代码实现一下

#include<stdio.h>
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);
   int ret=fib(n);
   printf("%d\n",ret);
   return 0;
}

 但是我们发现,用递归的方式求斐波拉契数,是有问题的

 我们会发现,并没有输出结果,那究竟是怎么回事呢?

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

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

  我们发现 fib 函数在调用的过程中很多计算其实在一直重复。

如果我们把代码修改一下:

#include<stdio.h>
int count=0;//全局变量
int fib(int n)
{
   if(n==3)
   {
     count++;
     //在计算第20个斐波拉契数时,第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;
}

我们会发现,才计算到第20个斐波拉契数,第3个斐波拉契数就已经被重复计算了这么多次,可见,做的无用功非常多 

那我们如何改进呢?

在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出)这样的信息。

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

那如何解决上述的问题:

1. 将递归改写成非递归。

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

那下面,用迭代的方式来实现一下求斐波拉契数

#include<stdio.h>
int fib(int n)
{
   int a=1;
   int b=1;
   int c=1;
   while(n>=3)
   {
      c=a+b;
      a=b;
      b=c;
      n--;
   }
   return c;
}
int main()
{
   int n=0;
   scanf("%d",&n);
   int ret=fib(n);
   printf("%d ",ret);
   return 0;
}

我们会发现,这样的方式也是非常ok的

提示:

1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。

2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。

3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。


接下来,我们来看一个函数递归的经典题目——青蛙跳台阶 

一只青蛙可以一次跳1级台阶或者一次跳2级台阶,例如:跳上第1级台阶只有一种跳法:直接跳1级即可。跳上2级台阶,有两种跳法:每次跳1级,跳2次;或者一次跳2级。问:跳上第n级台阶有多少种跳法?

设台阶数为N

当N=1时 只有一种跳法

当N=2时 可以跳两次一层,也可以跳一次两层,就有两种跳法

当N=3时 当有三层台阶时,青蛙可以选择先跳一层,剩下两层台阶,所以此时就是有两层台阶时                   的跳法,有两种:当青蛙选择第一次跳上两层台阶时,剩下一层台阶,此时有一层台阶                   时的跳法,所以三层台阶时的方法有:两层台阶的方法+一层台阶的方法

当N=4时 (1)先跳一层:若先跳一层,则剩下三层

                                         就是三层台阶的跳法

               (2)先跳两层:若先跳两层,则剩下两层

                                         就是两层台阶的跳法

                  结果就是:三层台阶的方法+两层台阶的方法

当N=n时 n层台阶的方法为:n-1层台阶的方法+n-2层台阶的方法

下面,我们来写代码

#include<stdio.h>
int frog(int n)
{
   if(n==1)
   {
      return 1;
   }
   if(n==2)
   {
      return 2;
   }
   else
   {
      return frog(n-1)+frog(n-2);
   }
}
int main()
{
   int n=0;
   scanf("%d",&n);
   int ret=frog(n);
   printf("%d\n",ret);
   return 0;
}

 下面,我们把题目修改一下

  让青蛙一次可以跳多个台阶(大于2)

首先我们让青蛙一次可以跳3个台阶

当N=1时 有一种跳法

当N=2时 有两种跳法

当N=3时 青蛙可以选择第一次先跳一层台阶或者两层台阶或者三层台阶,那么就有(1,1,1),               (1,2),(2,1),(3)四种方法

当N=4时 青蛙选择第一次跳一层台阶时,剩下三层台阶,则为N=3时的方法

               青蛙选择第一次跳两层台阶时,剩下两层台阶,则为N=2时的方法

               青蛙选择第一次跳三层台阶时,剩下一层台阶,则为N=1时的方法

 当N=4的所有方法为:N=3的所有方法+N=2的所有方法+N=1的所有方法

当N=n时 n层台阶的方法为:n-1层的所有方法+n-2层的所有方法+n-3层的所有方法

一样,我们下面还是来写代码

#include<stdio.h>
int frog(int n)
{
   if(n==1)
   {
     return 1;
   }
   if(n==2)
   {
     return 2;
   }
   if(n==3)
   {
     return 4;
   }
   else 
   {
     return frog(n-1)+frog(n-2)+frog(n-3);
   }
}
int main()
{
   int n=0;
   scanf("%d",&n);
   int ret=frog(n);
   printf("%d\n",ret);
   return 0;
}

                                   本质上,青蛙跳台阶问题就是斐波拉契数列问题                                    


好啦,小雅兰今天的内容就到这里了,今天终于写了一篇关于C语言的文章呀,花了小雅兰很多时间哟,未来也会继续加油呀,争取把C语言和C++学好。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/188615.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

拼多多控价,低价投诉,不被支持怎么办

治理电商平台上的低价乱价链接&#xff0c;首选方法是沟通调整&#xff0c;成本低、见效快。针对沟通调整不成功的&#xff0c;再进行平台投诉&#xff0c;通过电商平台的力量对其进行约束。 但是&#xff0c;平台投诉一定要注意一点&#xff1a;不要以低价为理由&#xff01;…

原命题和逆否命题

看Boyd的凸优化看到这样一个证明&#xff1a; 从左到右的证明是 使用了一个逆否命题的方法进行证明&#xff0c;有点忘记了原命题和逆否命题之间的相互转换&#xff0c;记录一下。 简单形式命题 简单形式命题没有全称量词∀\forall∀和存在两次∃\exists∃&#xff0c;也没有…

字符流、字节流、缓冲流、序列化流

1.文件拷贝的基本代码 2.一次读取多个字节 3.异常处理 但是上述的释放过程太繁琐&#xff0c;于是便用AutoCloseable接口简化释放过程 JDK7: JDK9: 4.各种字词计算机存储规则 所以底层二进制文件就是根据正负来区别英文和中文的。 5.乱码 6.字符流 7.字符输入流和输出流底层原理…

ASEMI桥式整流电路KBP206特性参数的计算

编辑-Z 什么是桥式整流电路&#xff1f; 桥式整流电路由四个二极管以闭环“桥式”配置连接&#xff0c;以产生所需的输出。这种桥式电路的主要优点是不需要特殊的中央抽头变压器&#xff0c;从而减小了尺寸和成本。单个次级绕组连接到二极管桥网络的一侧&#xff0c;负载连接到…

【正点原子FPGA连载】第二十一章根文件系统构建 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Linux开发指南

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十一章根文件…

ch3_1系统总线的概念

1. 总线的基本概念 总线概念&#xff1a; 是连接各个部件的信息传输线&#xff0c;是各个部件共享的传输介质&#xff1b; 1.1 为何需要总线 总线结构便于增减 外设&#xff0c; 同时 减少了信息传输线的条数。 总线是信号的公共传输线&#xff0c; 1.2 总线上信息的传送 …

深入理解堆与优先队列

目录一、什么是堆&#xff1f;二、堆的实现2.1 上滤与下滤2.2 堆的常用操作2.3 建堆三、堆排序四、优先队列References一、什么是堆&#xff1f; 堆&#xff08;Heap&#xff09;是一种特殊的完全二叉树&#xff0c;满足性质&#xff1a;除叶节点外每个节点的值都大于等于&…

【Window 入侵排查】

Window 入侵排查1、文件的排查1.1 开机启动有无异常文件启动1.2 对系统敏感文件路径的查看1.3 查看Recent1.4 查看文件时间1.5 webshell 文件排查2、进程、端口排查2.1 查看进程2.2 进程排查2.3 使用powershell 进行查询2.4 使用WMIC 命令进行排查3、检查启动项、计划任务、服务…

全志V85X系列芯片PCB设计需要注意些什么?

全志V85X &#xff08;包括V853、V853S、V851S、V851SE等&#xff09;是一颗面向智能视觉领域推出的新一代高性能、低功耗的处理器SOC&#xff0c;可广泛用于智能门锁、智能考勤门禁、网络摄像头、行车记录仪、智能台灯等智能化升级相关行业。V85X 集成ARM Cortex-A7和RISC-V E…

一个跨平台执行外部命令的C#开源库

更多开源项目请查看&#xff1a;一个专注推荐.Net开源项目的榜单 对于我们程序员来说&#xff0c;在日常开发项目中&#xff0c;调用外部的命令是非常常见的&#xff0c;比如调用批处理命令、调用其他应用&#xff0c;这里面就涉及到进程的通讯、管理、启动、取消等一些操作&am…

Spring Native打包本地镜像,无需通过Graal的maven插件buildtools

简介 在文章《GraalVM和Spring Native尝鲜&#xff0c;一步步让Springboot启动飞起来&#xff0c;66ms完成启动》中&#xff0c;我们介绍了如何使用Spring Native和buildtools插件&#xff0c;打包出本地镜像&#xff0c;也打包成Docker镜像。本文探索一下&#xff0c;如果不通…

一文细说Linux Out Of Memory机制

有时候我们会发现系统中某个进程会突然挂掉&#xff0c;通过查看系统日志发现是由于 OOM机制 导致进程被杀掉。 今天我们就来介绍一下什么是 OOM机制 以及怎么防止进程因为 OOM机制 而被杀掉。 什么是OOM机制 OOM 是 Out Of Memory 的缩写&#xff0c;中文意思是内存不足。而…

【CLYZ集训】人人人数【数学】

思路&#xff1a; 先转转转&#xff0c;把答案变成求每种数的出现次数都小于i的方案书除以Cnm−1mC_{n m - 1}^{m}Cnm−1m​ 对于每个1到m中的数&#xff0c;设每个数的出现次数为xi&#xff0c;则所有x加起来要等于m&#xff0c;且都小于i。 容斥&#xff0c;设其中k个不小于…

目标检测算法——YOLOV8——算法详解

一、主要贡献 主要的创新点&#xff1a;其实到了YOLOV5 基本创新点就不太多了&#xff0c;主要就是大家互相排列组合复用不同的网络模块、损失函数和样本匹配策略。 Yolo v8 主要涉及到&#xff1a;backbone 使用C2f模块&#xff0c;检测头使用了anchor-free Decoupled-head&a…

HTB_Unified_log4j_jndi注入mongodb修改用户hash

文章目录信息收集漏洞复现漏洞验证漏洞利用提权信息收集 nmap -sV -v 这次扫描时间很长&#xff0c;因为默认只扫 1000 个常用端口&#xff0c;如果扫到大端口就会自动扫描全端口&#xff0c;可以自行加速 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (…

Markdown语法-从基础到进阶

时代在进步&#xff0c;越来越多的人和软件开始使用Markdown进行文字编辑&#xff0c;其编辑方便性让很多人爱不释手。但是&#xff0c;不可避免的问题是&#xff0c;在编辑的时候&#xff0c;经常会需要去google,毕竟&#xff0c;习惯了office的可视化操作符号&#xff0c;很多…

在成都Java培训班学习五个多月有用吗?

不知道“有用”的标准是什么&#xff0c;是能入行上岗工作&#xff0c;还是想只通过几个月的培训一跃成为资深开发攻城狮&#xff1f;这里不得不给大家泼瓢冷水&#xff0c;短期培训能让你对口上岗工作就很不错了&#xff1b;想要成为技术大佬&#xff1f;大学里面四年都没能让…

2023年南京Java培训机构排行榜上线,犹豫的小伙伴们看过来!

2022年&#xff0c;JRebel发布了《2022年Java发展趋势和分析》&#xff0c;它通过调研问卷的方式总结的报告&#xff0c;涉及了不同国家、不同岗位、不同公司规模、不同行业&#xff0c;相对来说&#xff0c;该调查报告是有一定参考意义的。数据显示&#xff0c;Java这一语言在…

使用Chisel搭建Systolic Array

最近听到非常多人吹Chisel&#xff0c;为了方便快速做算法实现&#xff0c;就去尝试学了下&#xff0c;发现确实很香&#xff0c;有种相见恨晚的感觉。今天是使用Chisel搭建个脉动阵列&#xff08;Systolic Array, SA&#xff09;[1]&#xff0c;脉动阵列是神经网络中最基础也是…

用递归玩转简单二叉树

前言&#xff1a; 数据结构学到二叉树&#xff0c;就进入到了有难度的部分了&#xff0c;但难度对应着重要性&#xff0c;其重要性也不言而喻了。这节我会介绍用C语言实现递归方法的二叉树的一些重要基本功能&#xff0c;在二叉树中又属于基础知识&#xff0c;有需要的各位必须…