函数(3)

简介: 函数(3)

迭代(非递归):循环就是一种迭代,迭代可以理解成循环

我们什么时候优先用迭代呢?

(1)递归层次太深时,栈溢出程序会崩溃

(2)递归的过程中有很多计算在一直重复,程序效率低

7.3.1 练习3

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

代码1——用递归写

#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", ret);
  return 0;
}

《函数栈帧

    每一个函数调用都会为本次函数调用分配内存空间(是内存的栈区),为本次函数调用分配的内存空间就被称为这函数调用的栈帧空间。

函数栈帧的创建和递归!



递归层次太深,就会栈溢出程序崩溃!

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

更改:代码2——迭代

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

好处:这时程序是不会栈溢出,也就不会崩溃!(虽然结果是错的,但是不会崩——int的数据范围小了,这讲的是思想)

当递归层次太深时——非递归


7.3.2  练习4

求第n个斐波那契数

代码1——递归

#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 个斐波那契数字的时候特别耗费时间

为什么呢?


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

我们更改一下代码——看看第3个斐波那契数被重复计算了多少次


#include<stdio.h>
int count = 0;
int Fib(int n)
{
  if (3 == n)//统计第三个斐波那契数被计算的次数
  {
    count++;
  }
  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("%d\n", count);
  return 0;
}


从代码结果:第3个斐波那契数重复了39088169次,——效率低

改成迭代

代码2——迭代

减少了重复运算,提高了效率

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

总结:

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

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

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


递归存在的问题:

(1)递归层次太深时, 那就会报错: stack overflow (栈溢出) 这样的信息。

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

(2)递归的过程中存在大量的重复计算(效率低)

那如何解决上述的问题:

1. 将递归改写成非递归。(迭代就是非递归)

2. 使用 static 对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代

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


相关文章
|
机器学习/深度学习 TensorFlow 算法框架/工具
基于深度学习的图像分类:使用卷积神经网络实现猫狗分类器
基于深度学习的图像分类:使用卷积神经网络实现猫狗分类器
615 0
|
存储 安全 网络安全
Windows安全防护:构建多层防御体系,守护系统安全
Windows系统的安全性对于保护用户个人信息和企业业务连续运行至关重要。面对日益严峻的网络威胁,我们需要构建多层防御体系,通过采用系统内置的安全防护措施、用户可采取的安全保护措施以及加强用户教育与培训、实施严格的访问控制策略、定期进行系统安全评估与审计、建立应急响应机制以及采用先进的安全防护技术等方式
964 57
|
9月前
|
Java Go C#
Matplotlib 散点图
Matplotlib 散点图
147 0
Matplotlib 散点图
|
前端开发 JavaScript 小程序
【JS】async、await异常捕获,这样做才完美
本文通过生动的例子说明了在JavaScript中使用async/await时,不捕获异常可能导致的问题,并介绍了三种处理异步调用异常的方法:try-catch、使用Promise的`.catch`以及`await-to-js`插件库。通过这些方法,可以有效避免异常导致的程序中断,提升代码的健壮性和可读性。
257 0
【JS】async、await异常捕获,这样做才完美
|
XML 搜索推荐 Java
Elasticsearch集成到Spring Boot项目
将Elasticsearch集成到Spring Boot项目中,可以方便地实现数据的搜索、分析等功能。
517 2
|
数据采集 数据安全/隐私保护
如何使用异常处理机制捕获和处理请求失败的情况
在爬虫开发中,我们经常会遇到请求失败的情况,比如网络超时、连接错误、服务器拒绝等。这些情况会导致我们无法获取目标网页的内容,从而影响爬虫的效果和效率。为了解决这个问题,我们需要使用异常处理机制来捕获和处理请求失败的情况,从而提高爬虫的稳定性和稳定性。
279 0
如何使用异常处理机制捕获和处理请求失败的情况
|
Linux Docker 容器
保姆级教程:aarch安装docker
保姆级教程:aarch安装docker
1355 0
|
监控 网络协议 安全
eve-ng中模拟飞塔HA测试实验及理论
eve-ng中模拟飞塔HA测试实验及理论
492 1
eve-ng中模拟飞塔HA测试实验及理论
|
存储 前端开发 JavaScript
前端祖传三件套JavaScript的对象之常用引用类型的Object
在 JavaScript 中,对象是一种非常重要的数据类型,它可以用来存储和处理复杂的数据结构。在对象中,引用类型 Object 是一种常用的类型,它可以用来封装各种不同类型的数据,并提供了许多方便的方法来操作这些数据。本文将介绍 Object 引用类型的概念、使用方法以及一些常见的注意事项。
185 0
数据结构一个小白的练级之路【链表的分割】题目参考
数据结构一个小白的练级之路【链表的分割】题目参考