递归优化的这三种方式你知道吗?

2023-11-02

估计找工作的,都会碰到面试官老是问道“递归算法”,感同身受,前段时间面试的时候,就有一家问道这个问题,是非常典型的问题。在前面一篇世界上有哪些代码量很少,但很牛逼很经典的算法或项目案例?,递归应该算是比较“经典”的算法。

1.从 斐波那契数列开始说起

波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,是指这样一个数列

递推公式如图:

递推公式

有没有直接计算的公式?当然有

public int Fibo(int n)
{
    double c = Math.Sqrt(5);
    return (int)((Math.Pow((1 + c) / 2, n) - Math.Pow((1 - c) / 2, n)) / c);
}

1.最常见的递归

第一项和第二项的值均为1,后面每项的值都是前两项值之和,所以我们很多人基本上都会使用递归来实现,常见的算法如下:

        public int Fibo(int n)
        {
           if (n == 1 || n == 2)
           {
                return 1;
           }
           return Fibo(n - 2) + Fibo(n - 1);
        }

但这种做法并不能完全解决问题,因为最大允许的递归深度跟当前线程剩余的栈空间大小有关,事先无法计算。如果实时计算,代码过于复杂,就会影响代码的可读性。所以,如果最大深度比较小,比如 10、50,就可以用这种方法,否则这种方法并不是很实用。

ps:递归代码要警惕重复计算

除此之外,使用递归时还会出现重复计算的问题。刚才我讲的第二个递归代码的例子,如果我们把刚才我讲的第二个递归代码的例子,如果我们把整个递归过程分解一下的话,那就是这样的:n 越大,这段代码执行效率越低通过测试一下看他的效率如何

            Stopwatch sw = new Stopwatch();
            sw.Start();
            var result = Fibo(40);
            sw.Stop();
            Debug.WriteLine("n=100;result="+result+";耗时:"+sw.ElapsedMilliseconds);
n=10;result=55;耗时:2715
n=40;result=102334155;耗时:4673

如果n再稍微大一点,所消耗的时间是成指数级增长的,比如n=64的时候,所消耗的时间可能是两三年!不信的话,你可以试试!

我们会发现f(n)这个方法被调用了很多次,而且其中重复率非常之高,也就是说被重复计算了很多次,如果n稍微大一点这棵树会非常庞大。这里我们可以看出,每个节点就需要计算一次,总计算的次数就是该二叉树节点的数量,可见其时间复杂度为O(2n),是指数级的,其空间复杂度也就是该二叉树的高度,为O(n)。这样来看,我们应该就清楚了,为什么这段代码效率如此低下了吧。

2.数组保存法

我们应该避免无数次重复的计算

为了避免无数次重复,可以从n=1开始往上计算,并把每一个计算出来的数据,用一个数组保存,需要最终值时直接从数组中取即可,算法如下:

public int fib(int n) {
    int[] fib = new int[n];
    fib[0] = 1;
    fib[1] = 1;
    for (int i = 2; i < n; i++) {
        fib[i] = fib[i - 2] + fib[i - 1];
    }
    return fib[n - 1];
}

测试一下结果

n=10;result=55;耗时:0
n=40;result=102334155;耗时:0
n=1000000;result=1884755131;耗时:5

毫秒级,几乎忽略不计的,当计算100万时,也就5毫秒

3.滚动数组法

尽管上述算法已经很高效了,但我们还是会发现一个问题,其实整个数组中,每次计算时都只需要最新的3个值,前面的值计算完后就不再需要了。比如,计算到第10次时,需要的数组空间只有第8和第9两个空间,前面第1到第7个空间其实就不再需要了。所以我们还可以改进,通过3个变量来存储数据,算法如下:

        public int Fibo3(int n)
        {
            int first = 1;
            int second = 1;
            int third = 2;
            for (int i = 3; i <= n; i++)
            {
                third = first + second;
                first = second;
                second = third;
            }
            return third;
        }

时间复杂度仍然为O(n),而空间复杂度为常量级别3,即空间复杂度为0,所以这种方法是非常高效的。

3.尾递归法

首先我们来了解一下什么是尾调用。

在计算机科学里,尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回的情形。这种情形下该调用位置为尾位置。

 /// n 第n个数
 /// first 第n个数
 /// second 第n与第n+1个数的和
 /// @return 返回斐波那契数列值
public int Fib5(int n, int first, int second) {
    if (n <= 1) {
        return first;
    } else {
        return fib5(n-1,second,first+second);
    }
}

,也都是通过两个变量保存计算值,传递给下一次进行计算,递归的过程中也是根据n值变化逐步重复运算,和循环差不多,时间复杂度和空间复杂度也都一样,优雅了很多。

我们知道递归调用是通过栈来实现的,每调用一次函数,系统都将函数当前的变量、返回地址等信息保存为一个栈帧压入到栈中,那么一旦要处理的运算很大或者数据很多,有可能会导致很多函数调用或者很大的栈帧,这样不断的压栈,很容易导致栈的溢出。

还有一种优化思路就是矩阵快速幂法,可参考链接 https://blog.csdn.net/computer_user/article/details/86927209

回复 【关闭】学关闭微信朋友圈广告

回复 【实战】获取20套实战源码

回复 【福利】获取最新微信支付有奖励

回复 【被删】学查看你哪个好友删除了你巧

回复 【访客】学微信查看朋友圈访客记录

回复 【卡通】学制作微信卡通头像

回复 【python】学微获取全套0基础Python知识手册

回复 【2019】获取2019 .NET 开发者峰会资料PPT

winform已死?这8个Winform开源项目还有多少人在用?


VS Code 1.47 发布!官方版 Settings Sync 终于来了!


张善友:最新.NET程序员省份分布排名


Pandownload关闭了,百度网盘真的提速高达10Mb/s?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

递归优化的这三种方式你知道吗? 的相关文章

随机推荐

  • SpringCloud集成RocketMQ实现事务消息方案

    前边的话 当前SpringCloud作为微服务开发的首选开源方案提供了完善的微服务开发技术套件 不过针对分布式领域的难题 分布式事务控制并没有成熟的方案 本篇将介绍作为柔性事务控制的优秀方案RocketMQ的使用原理和方法 通过本案例的学习
  • 升级你的GitHub终端认证方式:从密码到令牌

    升级你的GitHub终端认证方式 从密码到令牌 前言 GitHub官方在2021年8月14日进行了一次重大改变 它将终端推送代码时所需的身份认证方式从密码验证升级为使用个人访问令牌 Personal Access Token 这个改变引起了
  • 三角剖分算法(delaunay)

    开篇 在做一个Low Poly的课题 而这种低多边形的成像效果在现在设计中越来越被喜欢 其中的低多边形都是由三角形组成的 而如何自动生成这些看起来很特殊的三角形 就是本章要讨论的内容 项目地址 https github com zhiyis
  • 阿里云OSS对象存储上传文件(一)SDK安装

    因为实际项目需求 需要使用阿里云oss的对象存储来上传文件 在写代码操作之前 需要先安装SDK 编译你能使用的lib 其实前后找了不少文章 但都不太细致 所以分享一下我本人使用的经验 不代表适用所有人 仅供参考 环境是windows系统 v
  • 安装使用NVIDIA-Docker——可使用GPU的Docker容器

    参考网址 https www cnblogs com wuchangsoft p 9767074 html nvidia docker是一个可以使用GPU的docker nvidia docker是在docker上做了一层封装 通过nvid
  • 【LeetCode题解】子序列问题

    文章目录 参考资料 子序列问题模板 动态规划 一 两种思路 例题 128 最长连续序列 思路一 代码 动态规划设计 300 最长递增子序列 动态规划设计 1143 最长公共子序列 动态规划设计 516 最长回文子序列 392 判断子序列 参
  • Java发布webservice

    先附上一个webservice的视频教程 链接 https pan baidu com s 1qesv A7cp zYsL7fE5nmFw 提取码 3d6k 创建服务端 提供接口 方式一 创建一个web工程 创建一个ServiceHello
  • C++ list容器

    1 list容器基本概念 循环迭代器 链表的末尾指向链表的链头 链表的链头指向链表的链尾 链表迭代器支持前移和后移 也就是说支持 和 操作 但不支持 n 和 n 操作 不支持随机访问 2 链表构造函数 3 赋值和交换 4 大小操作 5 插入
  • 基于实例讲解lsqcurvefit参数用法

    本博文源于 数学建模 旨在讲解非线性最小二乘拟合的MATLAB实现 谈到matlab中非线性最小二乘拟合 就不得不提到lsqcurvefit与lsqnonlin 博文就讲解一下lsqcurvefit如何使用 一 函数基本用法讲解 x lsq
  • Element-UI开发指南--动画和组件基础(二)

    文章目录 内置过渡动画 fade 淡入淡出 zoom 缩放 collapse 展开折叠 组件 Layout 布局 基础布局 分栏间隔 混合布局 分栏偏移 对齐方式 响应式布局 基于断点的隐藏类 Row 属性 Col 属性 Container
  • 3.观察者模式C++用法示例

    观察者模式 一 观察者模式 1 作用 2 适用场景 3 实现要素 二 C 程序示例 一 观察者模式 观察者模式 Observer Pattern 是一种行为型设计模式 它定义了一种一对多的依赖关系 让多个观察者对象同时监听一个主题对象 当主
  • 十四届蓝桥杯java第一期模拟赛 大小写转换编译题

    问题描述 输入一个由小写英文字母组成的字符串 请将其中的元音字母 a e i o u 转换成大写 其它字母仍然保持小写 样例输入 lanqiao 样例输出 lAnqIAO 代码实现 public static void main Strin
  • 猜数_____c语言

    include
  • uni-app 系列之(三)—— 资源引用路径

    模板内引用静态资源 template 内引用静态资源 如 image video 等标签的 src 属性时 可以使用相对路径或绝对路径 如下
  • MATLAB实现在区域中节点随机分布

    假设在一个100 100的区域中 随机生成10个节点 并将节点的坐标保存在txt文件中 global n global是全局变量 n代表节点的数量 global xm 区域的长 global ym 区域的宽 xm 100 ym 100 gl
  • axios中的AJAX

    1 Axios发送AJAX请求 1 引入Axios bootcdn cn 搜索 Axios 复制标签链接到代码中 案例 页面三个按钮 GET POST 通用型方法AJAX 点击按钮分别实现其功能
  • shell脚本中自动化交互输入

    有的shell脚本需要交互输入 如果需要批量或者自动化 可以根据实际情况按照如下方法处理 1 重定向 这个方法很简单 把需要输入的内容按每行写入到文档中 然后运行脚本 vpncmd lt content 2 使用管道 echo e 3 n
  • wordpress archive.php,wordpress分类目录模板(archive.php)制作

    本课程视频是VIP会员课程 学习请进入VIP学习区 分类目录模板通常包括二种 一种是普通文章列表目录 一种是产品图片展示列表目录 文章列表目录是通过将分类下的文章标题通过无序列表的形式展示出来 如下图 产品图片列表目录是将产品的第一张图片自
  • numpy.random.uniform

    文档中的定义 numpy random uniform low 0 0 high 1 0 size None 由原型可知 无参调用时返回从0 1之间的均匀分布取样 numpy random uniform 只有一个参数时 若low 1 0
  • 递归优化的这三种方式你知道吗?

    估计找工作的 都会碰到面试官老是问道 递归算法 感同身受 前段时间面试的时候 就有一家问道这个问题 是非常典型的问题 在前面一篇世界上有哪些代码量很少 但很牛逼很经典的算法或项目案例 递归应该算是比较 经典 的算法 1 从 斐波那契数列开始