使用迭代优化递归程

封面:从斐波那契数列到递归.png

王有志,一个分享硬核Java技术的互金摸鱼侠
加入Java人的提桶跑路群:共同富裕的Java人

今天我们将会分析上篇文章中递归算法存在的问题,并通过迭代去优化。

递归存在的问题

上一篇中,我们计算了序号10以内的斐波那契数。今天为了清晰的展示递归解法存在的问题,我们试着计算序号为50的斐波那契数,如果电脑的性能较差的话,就不要尝试了。
图1:斐波那契数列执行时间.png
可以看到,从计算F(40)开始,递归解法的耗时如同“坐火箭”般上升,这种情况是我们无法忍受的。
在上一篇文章中,我们有一个练习就是优化递归求解第n个斐波那契数。那么今天,我们就一起看看通过递归求解,问题出现在哪里?
通过之前构建的F(6)的递归树,我们可以看到,递归求解斐波那契数时,存在大量重复的计算,例如:仅仅是计算F(2)就出现了5次。
那么比较容易想到的优化方案就是缓存计算结果,使用时再取出。因此我们引入缓存,减少重复计算,提升执行速度(和复杂度分析中的空间换时间呼应上了)。
我们来看下具体实现:

private static long fib_recursion_memory(int n) {
return fib_recursion_memory(n, new long[n + 1]);
}private static long fib_recursion_memory(int n, long[] memory) {if (n < 2) {return n;}if (memory[n] != 0) {return memory[n];}memory[n] = fib_recursion_memory(n - 1, memory) + fib_recursion_memory(n - 2, memory);return memory[n];
}

代码并不复杂,通过引入long类型数组,记录已经计算过的斐波那契数,以达到减少重复计算的目的。
除此之外,还有没有其他方法求解第n个斐波那契数?

迭代

我们在使用递归时,通常是将规模较大的问题拆分为规模较小的问题,依次求解组合的过程。反之我们也可以从最小规模的问题开始,逐步累积到规模较大的问题。
迭代正是这样一种方法。迭代是数学概念引入编程中的,非常容易与循环混淆。来看下百度百科中的定义:

迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。

既然说到非常容易与循环混淆,那么再来看看循环的定义:

循环是程序设计语言中反复执行某些代码的一种计算机处理过程,常见的有按照次数循环和按照条件循环。

通过定义我们很容易看出迭代与循环的差别,循环只是程序的重复执行,对计算结果没有要求,而迭代需要将每次计算结果应用到下一次循环中,所以可以说迭代只循环的子集

迭代求解第n个斐波那契数

事实上,递归都是可以改写为迭代的形式(不那么优雅),而且邓俊峰老师也在《数据结构》中说到:

实际上,属于尾递归形式的算法,均可以简捷地转换为等效的迭代版本。

这给了我们使用迭代改写递归求解斐波那契数列的理论基础,下面我们直接开始。
首先斐波那契数列的的递推公式是从第3项开始,因此我们需要处理第1,2项的特殊情况,代码如下:

if(n <= 0) {return 0
}if(n < 3) {return 1;
}

根据递推公式,如果计算n的值,我们需要知道 n−1 和 n−2 的值,那么我们声明3个变量,用p代表 n−2 ,用q代表 n−1 ,用result代表n,那么p设置为 F(1) ,q设置为 F(2) ,result设置为 F(3) ,代码如下:

int p = 1, q = 1, result = p + q;

现在我们已经有了3个变量,表示 F(1) 到 F(3) ,仅仅是这些,你已经可以计算出F(4)的值了:

p = q;
q = result;
result = p + q;

如果想要计算 F(n) 的值,我们只需要不断的重复计算 F(4) 的过程即可:

for(int i = 4; i <= n; i++) {p = q;q = result;result = p + q;
}

完整的代码如下:

private static int fib(int n) {
if(n <= 0) {return 0;
}if (n < 3) {return 1;
}int p = 1, q = 1, result = p + q;
for (int i = 4; i <= n; i++) {p = q;q = result;result = p + q;
}
return result;
}

随着循环的进行,每次计算的结果都会带入到下一次的循环,这就是定义中所指的每次迭代结果作为下一次迭代的初始值进行处理
至于通过迭代求解的斐波那契数列的时间复杂度,相信大家一眼就可以看出来了吧?
如果你了解动态规划的话,你很容易就能想到,迭代求解斐波那契数列就是简单的动态规划解法,不过这是后话,现在我们按下不表。

结语

今天的内容到这里就结束了,我们来回顾下都聊了哪些内容:
首先是回顾了递归求解斐波那契数列的问题,通过“记忆”优化了递归的执行速度,但是增加了空间复杂度。
然后为了更高效,我们引入了迭代,虽然我并不鼓励大家记忆概念和定义,但是你要明白相似概念的区别。
最后我们通过迭代的方式求解斐波那契数列,实现了 O(n) 复杂度。当然,斐波那契数列还有 O ( log ⁡ _ n ) O(\log\_{}{n}) O(log_n) 的解法,不过这不是我们今天的内容。

练习

我们来做几道简单的题目:

  • 剑指Offer 10-I 斐波那契数列
  • 53.最大子数组和
  • 70.爬楼梯
  • 121.买卖股票的最佳时机

如果最第53题和121题没有解出来也并没有关系,这里涉及到动态规划的知识,还没有接触到的小伙伴也不要着急,后面是有动态规划的专题的。


如果本文对你有帮助的话,还请多多点赞支持。如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核Java技术的金融摸鱼侠王有志,我们下次再见!

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

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

相关文章

【Leetcode】236.二叉树的最近公共祖先

一、题目 1、题目描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 示例1…

关于“Python”的核心知识点整理大全63

目录 20.2.11 使用 Git 跟踪项目文件 1. 安装Git 2. 配置Git 3. 忽略文件 .gitignore 注意 4. 提交项目 20.2.12 推送到 Heroku 注意 20.2.13 在 Heroku 上建立数据库 20.2.14 改进 Heroku 部署 1. 在Heroku上创建超级用户 注意 注意 20.2.11 使用 Git 跟踪项目文件…

Vue3-37-路由-组件内的路由守卫 onBeforeRouteLeave 和 onBeforeRouteUpdate

简介 组件内的路由守卫&#xff0c;实际上就是两个 API 方法。 他们与普通的守卫不同的是 &#xff1a; 他们是写在组件内的&#xff0c;在组件中监听路由的变化&#xff0c;不是全局的&#xff0c;比较灵活。 以下是两个 API 的功能说明&#xff1a;onBeforeRouteLeave() : 守…

Java中的序列化方法探索

.为什么要序列化 对象不序列化&#xff0c;能直接存储吗&#xff1f; 在 Java 中&#xff0c;序列化是将对象的状态信息转换为可以存储或传输的形式&#xff08;例如&#xff0c;转换为字节流&#xff09;的过程。在对象数据需要在网络上传输或需要在磁盘上持久化存储时&#…

指针的含义、表示、规范、存储、运用

指针的含义、表示、规范、存储、运用 指针的含义指针的表示指针的规范先声明再定义声明和定义一起表示错误表示 指针的存储理解一个变量的存储过程和原理理解一个指针的存储过程和原理理解多个指针的存储过程和原理 指针的运用 指针的含义 表示某个变量或数据所在的内存地址 注…

使用tailscale访问对端局域网上的其他设备

当tailscale客户端应用程序直接安装在组织中的每个客户端、服务器和虚拟机上时&#xff0c;Tailscale 效果最佳。这样&#xff0c;流量就会被端到端加密&#xff0c;并且无需配置即可在物理位置之间移动机器。 但是&#xff0c;在某些情况下&#xff0c;你不能或不想在每台设备…

Linux第18步_安装“Ubuntu系统下的C语言编GCC译器”

Ubuntu系统没有提供C/C的编译环境&#xff0c;因此还需要手动安装build-essential软件包&#xff0c;它包含了 GNU 编辑器&#xff0c;GNU 调试器&#xff0c;和其他编译软件所必需的开发库和工具。本节用于重点介绍安装“Ubuntu系统下的C语言编译器GCC”和使用。 1、在安装前…

图片纹理贴图

/* * 当需要给图形赋予真实颜色的时候&#xff0c;不太可能为没一个顶点指定一个颜色&#xff0c;通常会采用纹理贴图 * 每个顶点关联一个纹理坐标 (Texture Coordinate) 其它片段上进行片段插值 * */#include <iostream> #define STBI_NO_SIMD #define STB_IMAGE_IMPLE…

【嵌入式移植】1、Ubuntu系统准备

Ubuntu系统准备 虚拟机与Ubuntu安装下载Ubuntu创建虚拟机系统配置 虚拟机与Ubuntu安装 嵌入式移植通常使用Linux操作系统的环境&#xff0c;使用Linux下的交叉编译工具链对BootLoader、kernel以及应用程序进行编译&#xff0c;然后下载运行。当然也可以通过各类IDE或者Windows…

从文本(.txt)文件中读取数据时出现中文乱码

前言 当需要从记事本中读取数据时&#xff0c;发现读取的数据会出现中文乱码&#xff0c;我尝试了C和C读取文件&#xff0c;发现都是这样。 乱码原因 文本文件的保存默认使用UTF-8编码方式&#xff0c;而VS编译器的编码方式是GBK&#xff0c;所以不同的编码方式导致了乱码。…

【leetcode】力扣算法之删除链表中倒数第n个节点【中等难度】

删除链表中倒数第n个节点 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 用例 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 输入&#xff1a;head …

各种锁的概述

乐观锁与悲观锁 悲观锁指对数据被外界修改持保守态度&#xff0c;认为数据很容易就会被其他线程修改&#xff0c;所以在数据被处理前先对数据进行加锁&#xff0c;并在整个数据处理过程中&#xff0c;使数据处于锁定状态。 悲观锁的实现往往依靠数据库提供的锁机制&#xff0…

计算机体系结构期末复习流程大纲

1.存储器和cache 存储器的容量、速度与价格之间的要求是相互矛盾的&#xff0c;速度越快&#xff0c;没bit位价格越高&#xff0c;容量越大&#xff0c;速度越慢&#xff0c;目前主存一般有DRAM构成。 处理器CPU访问存储器的指标&#xff1a; 延迟时间&#xff08;Latency&am…

【C++】—— 工厂模式详解

目录 &#xff08;一&#xff09;工厂模式的特点 &#xff08;二&#xff09;工厂模式分类 1、简单工厂模式 2、工厂方法模式 3、抽象工厂模式 &#xff08;三&#xff09;总结与回顾 &#xff08;一&#xff09;工厂模式的特点 1、优势 ⼯⼚模式是⼀种创建型设计模式&a…

快速入门Visual Studio 2022开发.Net Framework研发环境指南

IDE工具 Visual Studio 2022 Vs2022企业版 - VisualStudioSetup.exe Visual Studio Code VSCodeUserSetup-x64-1.66.2.exeVSCodeUserSetup-x64-1.67.0-insider.exe IDE环境 编程字体YaHei.Consolas YaHei.Consolas.1.12.ttf IDE插件 Visual Studio Code常用插件 Chinese…

django项目基础后端功能使用

参考材料 Django新手项目实例-CSDN博客 一、django安装 pip3 install django 二、django项目新建 在目标目下执行 django-admin startproject testdjgo 执行完成后生成对应项目路径 三、django路由功能编写 /xxx/urls.py中编写路由信息&#xff0c;并且把路由转发到对应…

说出来你别不信,盲订问界M9的原因 你们想错了,他们只图这个

文|AUTO芯球 作者|李瑞 怎么还有人说华为是骗子&#xff1f; 华为一张海报说问界M9上市6天&#xff0c;大定超过3万台。有些人就说这是假的&#xff0c;反正没第三方数据&#xff0c;华为可以随便写。 我去&#xff0c;我作为一名大定问界M9的车主&#xff0c;就奉劝哪些黑子…

5.vue学习笔记(数组变化的侦测+计算属性+Class绑定)

文章目录 1.数组变化的侦测1.1.变更方法1.2.替换一个数组 2.计算属性计算属性缓存vs方法 3.Class绑定3.1.绑定对象3.2.多个对象的绑定形式3.3.绑定数组3.4.数组与对象 1.数组变化的侦测 1.1.变更方法 vue能够侦听响应式数组的变更方法&#xff0c;并在它们被调用时出发相关的…

Taro+vue3 实现电影切换列表

1.需求 我们在做类似于猫眼电影的小程序或者H5 的时候 我们会做到那种 左右滑动的电影列表&#xff0c;这种列表一般带有电影场次 2.效果 3.说明 这种效果在淘票票 猫眼电影上 都有的 &#xff0c;一般电影类型的H5 或者小程序 这个是都有的 第一是好看 第二是客观性比较好 …

Mysql InnoDB行锁深入理解

Record Lock记录锁 Record Lock 称为记录锁&#xff0c;锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的&#xff1a; 当一个事务对一条记录加了 S 型记录锁后&#xff0c;其他事务也可以继续对该记录加 S 型记录锁&#xff08;S 型与 S 锁兼容&#xff09;&#xff0c;…