Kotlin尾递归优化

article/2025/10/4 15:15:02

Kotlin尾递归优化

尾调用(Tail Call)是函数式编程的一个重要概念,本文介绍它的含义和用法。

1. 尾递归

​ 如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。

​ 简单地说,尾递归就是某个函数的最后一步是调用另一个函数,且在这一步中,除了调用函数外,前后没有其他操作。

​ 因此,尾递归就是递归的一种特殊形式。

//尾调用
fun tailCall(n : Int): Int{return f(n)
}//不是尾调用
fun notTailCall(n : Int): Int{return n + f(n)
}//不是尾调用
fun notTailCallEither(n : Int):Int{return f(n) + n
}
//这两种函数调用不是尾调用, 是因为它们在最后一步除了调用函数之外还有其它操作

​ 要注意的是,尾调用不一定出现在函数尾部,只要是最后一步操作即可

//尾调用
fun cumsum(n:Int, res:Long):Long = if (n == 1) n+res else cumsum(n - 1, n+res)
//尾调用
fun cumsum(n:Int, res:Long):Long {if (n == 1) return n+res else return cumsum(n - 1, n+res)
}
//这些函数结束前的最后一步都只有函数调用,因此是尾调用

我们通常利用编译器进行的尾调用优化来改善代码性能,解决因递归次数过多造成的栈溢出异常StackOverflowError

2. 尾调用优化

​ 尾调用之所以与其他调用不同,就在于它的特殊的调用位置。

​ 我们知道,函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个"调用栈"(call stack)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9UwV6aqT-1611578020866)(C:\Users\Administrator.DESKTOP-234JUNK\Desktop\尾递归优化.assets\image-20210125191519588.png)]

​ 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。

fun f() {val m = 1;val n = 2;return g(m + n);
}
f();// 等同于
fun f() {return g(3);
}
f();// 等同于
g(3);

​ 上面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除 f() 的调用记录,只保留 g(3) 的调用记录。

​ 这就叫做"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义。

3. 递归函数的改写

有示例累加函数,计算从1到n之和

fun cumsum(n:Int):Long = if (n == 1) 1 else n+cumsum(n-1)val res = cumsum(n)

当输入的n数值过大时,会造成栈溢出异常StackOverflowError:(C:\Users\Administrator.DESKTOP-234JUNK\Desktop\尾递归优化.assets\image-20210125192314000.png)]

因此,我们需要对这样的递归函数改写为尾递归函数,确保最后一步只调用自身

因此我们可以得到以下代码:

fun cumsum(n:Int, res:Long):Long = if (n == 1) n+res else cumsum(n - 1, n+res)var res = 0
res = cumsum(n, res)

这样,我们完成了从普通递归函数到尾递归函数的改写,但这样的改写降低了可读性,其他人在函数使用时会造成困惑:为什么计算n的累加和需要传入n和0呢?

要解决代码的使用问题,我们可以采取两种办法:

3.1 函数封装

在尾递归函数外提供一个正常形式的函数:

//尾递归函数计算累加和
fun cumsum(n:Int, res:Long):Long = if (n == 1) n+res else cumsum(n - 1, n+res)//正常形式函数调用尾递归函数
fun getCumsum(n:Int) = cumsum(n, 0)val res = getCumsum(n)

3.1.1 柯里化

​ 柯里化(Currying),就是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数

fun cumsum(n:Int, res:Long):Long = if (n == 1) n+res else cumsum(n - 1, n+res)
fun currying(func:(Int, Long)->Long, res:Long) = fun(n:Int) = func(n, res)val curriedCumsum = currying(::cumsum, 0)
val res = curriedCumsum(n)

3.2 参数默认值

通过使用参数默认值,我们可以很轻松地将传入两个参数转为传入一个参数

fun cumsum(n:Int, res:Long = 0):Long = if (n == 1) n+res else cumsum(n - 1, n+res)val res = cumsum(n)

4. Kotlin尾递归函数优化

在Kotlin中,对尾递归函数特别进行了优化,要使用优化,除了要将递归函数形式改写为尾递归函数外,还要在函数前加上关键字tailrec

tailrec fun cumsum(n:Int, res:Long = 0):Long = if (n == 1) n+res else cumsum(n - 1, n+res)

注意:如果没有添加关键字tailrec,即使将递归函数形式改写为尾递归函数,Kotlin也不会对函数进行尾递归优化


http://chatgpt.dhexx.cn/article/1wJGxkIN.shtml

相关文章

c语言尾递归,C语言——递归与尾递归

在计算机科学领域中,递归式通过递归函数来实现的。程序调用自身的编程技巧称为递归( recursion)。 一个过程或者函数在其定义或者说明中有直接或者间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题类似的规模较小的问题来求解&…

php 尾递归,又见尾递归

这几天看到几篇关于尾递归的文章,之前对尾递归没有多大概念,所以回头研究了一下尾递归。 尾递归的概念 尾递归(Tail Recursion)的概念是递归概念的一个子集。对于普通的递归,由于必须要记住递归的调用堆栈,由此产生的耗用是难以估…

javascript尾递归优化

JS中的递归 我们来看一个阶乘的代码 function foo( n ){if(n < 1){return 1;}return n * foo( n - 1 ); }foo(5); // 120下面分析一下&#xff0c;代码运行过程中,执行上下文栈是怎么变化的 这个代码是在全局作用域中执行的&#xff0c;所以在foo函数得到执行之前&#x…

Scala尾递归

一、首先来简单介绍一下递归和尾递归 1.递归&#xff1a; 简单来说就是在函数内部调用函数本身来完成函数体。对于返回值的要求并不很严格。 递归的缺点&#xff1a;递归效率比较低&#xff0c;调用次数过多还会出现栈溢出的问题。 2.尾递归&#xff1a; 尾递归的核心思想&…

java 递归 尾递归_递归和尾递归

C允许一个函数调用其本身&#xff0c;这种调用过程被称作递归(recursion)。 最简单的递归形式是把递归调用语句放在函数结尾即恰在return语句之前。这种形式被称作尾递归或者结尾递归&#xff0c;因为递归调用出现在函数尾部。由于为递归的作用相当于一条循环语句&#xff0c;所…

递归算法和尾递归

一、递归算法 1、递归算法思想 递归算法是在程序中不断反复调用&#xff08;间接或直接地调用&#xff09;自身&#xff0c;来达到解决问题的算法。 递归是一个方法在其方法体内调用自身方法的调用方式。递归调用分为两种情况&#xff1a; 直接递归&#xff0c;即在方法中调用…

尾递归(Tail Recursion)

向上递归(Pass-Up Recursion) 先来看一下传统的递归 def sum(lst):"""把lst所有元素求和"""if len(lst) 0:return 0return lst[0] sum(lst[1:])加入sum([1,2,3]),则是先求sum([2,3])&#xff1b;而要求sum([2,3]),则要求sum([3]), 求sum([3]…

尾递归

什么是尾递归 什么是尾递归呢?(tail recursion), 顾名思议&#xff0c;就是一种“不一样的”递归&#xff0c;说到它的不一样&#xff0c;就得先说说一般的递归。对于一般的递归&#xff0c;比如下面的求阶乘&#xff0c;教科书上会告诉我们&#xff0c;如果这个函数调用的深…

【算法】递归:递归优化之尾递归

【算法】递归&#xff1a;递归优化之尾递归 引言&#xff1a;在以往我发过一篇过于通过分析法去理解递归求解递归的博客文章&#xff0c;那篇文章主要介绍了如何去求解递归问题。而在这篇文章中&#xff0c;我会介绍一下如何去优化递归&#xff0c;顺带还会去分析一下递归算法的…

DDD微服务架构设计第四课 DDD指导微服拆分和落地实现

07 在线订餐场景中是如何开事件风暴会议的&#xff1f; 微服务设计最核心的难题是微服务的拆分&#xff0c;不合理的微服务拆分不仅不能提高研发效率&#xff0c;反倒还使得研发效率更低&#xff0c;因此要讲究“小而专”的设计。“小而专”的设计意味着微服务的设计不是简单拆…

微服架构的论述?

搭建微服架构 一、什么是微服架构 简单的说就是将一个整体的应用按照一定的规则拆分成一个个独立的应用&#xff0c;这些独立的应用后面又组合成了一个整体的应用。 比如说一个博客系统&#xff0c;我可能包含了发表文章&#xff0c;用户登录&#xff0c;用户评论等功能&…

微服务架构是什么?

微服务架构是什么&#xff1f; 一文详解微服务架构最初的需求随着业务发展……是时候做出改变了没有银弹监控 - 发现故障的征兆定位问题 - 链路跟踪分析问题 - 日志分析网关 - 权限控制&#xff0c;服务治理服务注册于发现 - 动态扩容熔断、服务降级、限流熔断服务降级限流 测试…

SpringCloud微服之Nacos的学习

1:使用前提 第一步:解压启动Nocos SpringCloudAlibaba 推出了一个名为 Nacos 的注册中心&#xff0c;在国外也有大量的使用。 startup.cmd -m standalone 访问http://localhost:8848/nacos/ 第二步:服务注册 工程目录 在父工程中添加依赖 <dependency><groupI…

python注册nacos微服并使用gateway网关

业务需求&#xff1a;使用python flask框架和java spring boot框架共同注册到nacos中&#xff0c;在由spring cloud gateway分配路由。 flaskDome: from flask import Flaskapp Flask(__name__)app.route(/python) def test():return "这是python flask框架接口&#xf…

大开眼界,Jenkins结合SpringCloud+K8S,打通微服一条龙技术讲解

Jenkins 是目前最常用的持续集成工具&#xff0c;拥有近50%的市场份额&#xff0c;他还是很多技术团队的第一个使用的自动化工具。由此可见他的重要性&#xff01; 这份Jenkins宝典从入门介绍到结合DockerSpringCloudKubernetes&#xff0c;打通一条龙技术讲解&#xff0c;简直…

基于Ant DesignPro Vue + SpringBoot 前后端分离 - 后端微服化 + 接口网关 + Nacos

基于Ant DesignPro Vue SpringBoot 前后端分离 - 后端微服化 接口网关 Nacos 通过Ant DesignPro Vue SpringBoot 搭建的后台管理系统后&#xff0c;实现了前后端分离&#xff0c;并实现了登录认证&#xff0c;认证成功后返回该用户相应权限范围内可见的菜单。 后端采用Spri…

微服务消费端通过feign调用微服异常问题

在项目开发中,我们的调用方通过Feign调用微服时,如果微服出现业务异常(例如空指针,或抛出自定义的异常)和非业务异常(参数不合法4xx异常)都会进入到调用方的全局异常拦截器,抛出的code全部转换成了500,这样不友好 实际上只有业务异常feign才会转换成500错误且转成FeignExcepti…

大开眼界!Jenkins结合SpringCloud+K8S,打通微服一条龙技术讲解

Jenkins 是目前最常用的持续集成工具&#xff0c;拥有近50%的市场份额&#xff0c;他还是很多技术团队的第一个使用的自动化工具。由此可见他的重要性&#xff01; 这份Jenkins宝典从入门介绍到结合DockerSpringCloudKubernetes&#xff0c;打通一条龙技术讲解&#xff0c;简直…

从0搭建公司SpringCloud Alibaba 微服務鉴权中心服务(详细教程)

大家好&#xff0c;我是宝哥&#xff01; 鉴权中心服务 认识JWT json web token 是一个开放的标准 &#xff0c;它定义了一个种紧凑的&#xff0c;自包含的方式&#xff0c;用于作为json对象在各方之间安全的传输信息 服务器鉴权完成之后 会生成 json 对象 发送给客户端&#x…

腾讯云开源业界微服管理框架 Femas

导读 企业数字化向云原生演进过程面临诸多痛点&#xff0c;微服务框架不统一、协议多样化、语言异构&#xff0c;纷繁复杂的微服务技术栈&#xff0c;基础组件之间像一座座孤岛&#xff0c;各个基础组件的控制面不能互联&#xff0c;让用户的体验非常割裂&#xff0c;各种历史包…