递归算法及其时间复杂度分析

引言

“递归” 一词是比较专业的计算机术语,在现实生活中,有一个更可爱的词——“套娃”。如果把“递归算法”叫做“套娃算法”,或许可以减少一些恐惧程度。

套娃是有限的,同样,递归也是有限的,这和我们经常在影视作品中看到的“无限嵌套循环”是有很大区别的,递归一定存在某个可以返回的节点或条件,否则就会出现栈溢出错误(StackOverflowError)。

其实“套娃”这个词已经足以概括递归算法的本质,就是函数本身调用自身,直到找到一个可以返回的条件,再层层返回。参考《盗梦空间》《明日边缘》等。

递归算法一定可以改写成非递归的实现方式(迭代)。本篇博客将会展示一个简单的递归算法案例,并介绍评估递归算法时间复杂度的 Master公式

一、案例:数组中的最大值

正如前面所说,递归算法一定可以改写成迭代方式,那么也就是说,递归也属于迭代,只不过它每次迭代的内容,和上一次都是一致的。

假设一个数组,如何用递归的方式找到其中的最大值?

分析,我们可以使用一个类似二分的思路,将数组层层二分,左侧找一个值,右侧找一个值,二者取一个最大。反复这样的过程。

完整代码:

public class RecursionGetMax {public static int getMax(int[] arr, int L, int R) {if (arr == null || arr.length == 0)throw new IllegalArgumentException("参数异常");if (arr.length == 1)return arr[0];// arr[L..R]范围上只有一个数,直接返回,base caseif (L == R)return arr[L];// 2个元素以上int mid = L + ((R - L) >> 1);// 递归取得区域最大int leftMax = getMax(arr, L, mid);int rightMax = getMax(arr, mid + 1, R);return Math.max(leftMax, rightMax);}public static void main(String[] args) {int[] arr = {1, 2, 3, 5, 22, 5, 221, 4, 6, 43, 6, 21, 1, 3, 3, 9};int max = getMax(arr, 0, arr.length - 1);System.out.println(max);}
}

注意,这道题在设计时,我们首先要有二分是思路基础,然后我们思考,要设计这样一个函数,这个函数一定会接收一个需要查找的数组,而仅仅传递数组的话,那么就需要在方法中反复拷贝新数组,显然空间复杂度太高,因此,不妨提供额外的两个指针,方便我们选取数组上的数,这样每次传递指针的值,就可以避免每次传递截取的数组。

其次,基础条件也是关键,base case 是可以直接返回的条件,即当 L == R 时,代表我们已经无法再继续二分,因此可以直接返回,这就是最终的返回节点,如果没有这个条件,那么递归将持续执行下去,直到 StackOverflow 。因此,在设计递归方法的时候,一定要考虑好 base case

二、递归的底层执行与逻辑分析方式

2.1 递归的底层实现

在系统中,递归的实现是使用一个系统栈来完成的,当方法执行到自身调用时,系统会将“现场”保存到系统栈中,擦除局部变量,再重新跳到函数的开头继续执行。

例如,一个数组 arr = {4, 3, 8, 5, 7},初始 L = 0,R = 4,leftMax的返回值调用过程:

2.2 逻辑分析方式

对于一个希望使用递归实现的方法,我们并不需要每次都画出类似上图的栈结构来详细分析,只需要画出逻辑递归图即可:

例如,arr = {5, 3, 4, 9},初始 L = 0, R = 3:

三、递归算法的时间复杂度分析

由于递归算法是建立在一种不确定循环次数的情况下,有点类似 do-while 循环,因此,在分析递归算法复杂度形式的时候,只展开第一层的规模

例如,上述二分取值的方式,就可以写成这样的时间消耗形式:

T(N) = a * T(N/b) + T(N^d),其中 a,b,d 都是常数

具体的值就是:T(N) = 2 * T(N/2) + O(1) ,a = 2, b = 2, d = 0。

Master 公式,可以直接确定形如上面时间消耗形式的时间复杂度:

如果 logb a < d,复杂度为:O(N^d)

如果 logb a > d,复杂度为:O(N^(logb a))

如果 logb a == d,复杂度为:O(N^d * logN)

其中,logb a,指的是以b为底 a 的对数。案例中的问题,由于log2 > d ,1 >0 ,因此时间复杂度就是 O(N^log2) = O(N) 。

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

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

相关文章

算法设计中的基础常用代码

引言 本篇博客旨在记录一些基础算法知识的常见组合用法&#xff0c;以及何时使用&#xff0c;需要注意的问题等&#xff0c;长期更新。 为什么要这样总结呢&#xff1f;难道掌握了位运算、常用算法工具API的定义还不够吗&#xff1f; 这是因为某些知识比如 &、 |、 ~、 …

Redis —— 常用命令一览

引言 参考《菜鸟教程 Redis 常用命令》&#xff0c;其中红色为极其重要&#xff0c;蓝色为重要。 一、总览 二、key相关命令 三、String 相关命令 四、Hash 相关命令 五、List 相关命令 六、Set 相关命令 七、ZSet 相关命令

Redis 实用技术——消息发布和订阅

引言 发布订阅模型是redis的重要功能&#xff0c;它可以像网站动态一样&#xff0c;将消息发送到多个订阅者的主页里。 一、常用命令 二、消息格式 消息是一个有三个元素的多块响应&#xff1a; 如上图&#xff0c;发布者向 mysub 频道发送了一条消息&#xff0c;redis会返回…

Redis 实用技术——事务

引言 redis的事务不像关系型数据库的事务那样完整。 “快”是redis的特征&#xff0c;在事务管理的过程中&#xff0c;使用muti命令开启事务块&#xff0c;当输入多条命令后&#xff0c;再使用exec命令执行事务块中的全部命令。 Redis事务可以保证两件事&#xff1a; 1、隔…

排序算法——归并排序的相关问题

一、小和问题 问题描述&#xff0c;给定一个数组&#xff0c;如[1, 3, 2, 6, 5]&#xff0c;计算每个数左边小于自己的所有数的和&#xff0c;并累加。例如&#xff1a; 1左边没有数 3左边有一个小于自己的数 1 2左边有一个小于自己的数 1 6左边有三个小于自己的数 1 3 2 6…

经典数据结构——堆的实现

一、完全二叉树 堆是一种完全二叉树&#xff0c;什么是完全二叉树&#xff1f; 简单的说&#xff0c;一棵满二叉树表示的是所有节点全部饱和&#xff0c;最后一层全部占满&#xff1a; 而完全二叉树指的是满二叉树的最后一层&#xff0c;所有叶子节点都从左往顺序排满&#x…

排序算法 —— 堆排序

引言 此文基于《经典数据结构——堆的实现》中堆结构&#xff0c;实现一个以堆处理排序的算法。 一、算法思想 基于堆结构的堆排序的算法思想非常简单&#xff0c;循环获取大根堆中的最大值&#xff08;0位置的根节点&#xff09;放到堆的末尾&#xff0c;直到将堆拿空。 由…

经典数据结构——前缀树

引言 前缀树——trie /ˈtraɪ//树&#xff0c;也叫作“单词查找树”、“字典树”。 它属于多叉树结构&#xff0c;典型应用场景是统计、保存大量的字符串&#xff0c;经常被搜索引擎系统用于文本词频统计。它的优点是利用字符串的公共前缀来减少查找时间&#xff0c;最大限度…

排序算法 —— 计数排序

引言 计数排序是桶排序思想的一种具体实现&#xff0c;针对一些具有特殊限制的样本数据&#xff0c;如公司员工年龄&#xff0c;那么样本数据本身就一定在0~200之间&#xff0c;针对这样的数据&#xff0c;使用从0到200 的桶数组&#xff0c;桶的位置已经是有序的&#xff0c;…

Java多线程 —— 线程状态迁移

引言 线程状态迁移&#xff0c;又常被称作线程的生命周期&#xff0c;指的是线程从创建到终结需要经历哪些状态&#xff0c;什么情况下会出现哪些状态。 线程的状态直接关系着并发编程的各种问题&#xff0c;本文就线程的状态迁移做一初步探讨&#xff0c;并总结在何种情况下…

Java中的Unsafe

Java和C语言的一个重要区别就是Java中我们无法直接操作一块内存区域&#xff0c;不能像C中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C手动管理内存的能力。 Unsafe类&#xff0c;全限定名是sun.misc.Unsafe&#xff0c;从名字中我们可以看出来这个类对…

arm中断保护和恢复_浅谈ARM处理器的七种异常处理

昨天的文章&#xff0c;我们谈了ARM处理器的七种运行模式&#xff0c;分别是&#xff1a;用户模式User(usr)&#xff0c;系统模式System(sys)&#xff0c;快速中断模式(fiq)&#xff0c;管理模式Supervisor(svc)&#xff0c;外部中断模式(irq)&#xff0c;数据访问中止模式Abor…

Queue —— JUC 的豪华队列组件

目录引言一、Queue 的继承关系1.1 Queue 定义基础操作1.2 AbstractQueue 为子类减负1.3 BlockingQueue 阻塞式Queue1.4 Deque 两头进出二、Queue 的重要实现三、BlockingQueue 的实现原理四、Queue 在生产者消费者模式中的应用五、Queue 在线程池中的应用六、ConcurrentLinkedQ…

daad转换器实验数据_箔芯片电阻在高温应用A/D转换器中的应用

工业/应用领域高温&#xff1a;地震数据采集系统、石油勘探监测、高精度检测仪产品采用&#xff1a;V5X5 Bulk Metal (R) Foil芯片电阻案例介绍TX424是一个完整的4通道24位模数转换器&#xff0c;采用40脚封装。该设计采用最先进设计方案&#xff0c;两个双通道24位调节器和一个…

excel分段排序_学会这个神操作,报表填报不再五花八门,效率远超Excel

在报表工作人员的的日常工作中&#xff0c;常常要面临统计混乱的终端用户输入的问题。由于无法准确限制用户的输入内容&#xff0c;所以在最终进行数据统计时&#xff0c;常常会出现数据不合法的情况。为此需要花费大量的人力和时间核对校验数据。举个简单的例子&#xff0c;某…

IDEA——必备插件指南

目录一、Free-Mybatis-Plugin二、Lombok三、jclasslib Bytecode Viewer一、Free-Mybatis-Plugin 二、Lombok 三、jclasslib Bytecode Viewer 学习 class 文件的必备插件。 使用简单&#xff0c;安装后可以在菜单 View 中看到 show bytecode with jclasslib&#xff1a; 效果…

jitter 如何优化网络_如何做好关键词优化网络?

越来越多的传统企业开始建立自己的网站&#xff0c;进而不断的推广自己的产品。为了能够让自己的企业网站出现在搜索引擎的首页&#xff0c;现在最常用的手段就是竞价排名和关键词优化网络。往往很多企业会选择关键词优化网络这种方式来推广自己的网站&#xff0c;对于新手seoe…

python学生名片系统_Python入门教程完整版400集(懂中文就能学会)快来带走

如何入门Python&#xff1f;权威Python大型400集视频&#xff0c;学了Python可以做什么&#xff1f;小编今天给大家分享一套高老师的python400集视频教程&#xff0c;里面包含入门进阶&#xff0c;源码&#xff0c;实战项目等等&#xff0c;&#xff0c;不管你是正在学习中&…

JVM——详解类加载过程

导航一、过程概述二、Loading2.1 类加载器2.2 双亲委派机制2.3 类在内存中的结构三、Linking四、Initializing一、过程概述 java 源文件编译后会生成一个 .class文件存储在硬盘上。 在程序运行时&#xff0c;会将用到的类文件加载到 JVM 内存中。从磁盘到内存的过程总共分为三…

下载 Java 学习的权威文档

JVMS 和 JLS 文档的下载 快速直达&#xff1a; https://docs.oracle.com/javase/8/ --> Java Language and Virtual Machine Specifications jvm specification 和 java language specification 是Java 学习的两个最权威的文档。如果你用的是 Java 8&#xff0c;就可以去下载…