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

article/2025/10/4 17:43:56

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

引言:在以往我发过一篇过于通过分析法去理解递归求解递归的博客文章,那篇文章主要介绍了如何去求解递归问题。而在这篇文章中,我会介绍一下如何去优化递归,顺带还会去分析一下递归算法的性能,这篇文章的目的是一个小小的分享,希望大家能在此有收获。

摘要:文章首先会先通过斐波那契数列来复习在以往介绍的解决递归的方法,然后在通过这个例子去分析递归法的不足之处,最后将会介绍一下关于递归的优化策略,并举例实现。


文章目录

  • 【算法】递归:递归优化之尾递归
    • 一. 递归法求斐波那契数列
    • 二. 递归法的不足
    • 三. 递归优化——尾递归
    • 四. 总结尾递归书写方法
    • 五. 总结

一. 递归法求斐波那契数列

  1. 问题呈现

    写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。

    斐波那契数列的定义如下:F(1) = 1, F(2) = 1,F(N) = F(N - 1) + F(N - 2), 其中 N > 1。

  2. 方法复习:通过分析问题,回答如何分解问题成为相似子问题,如何解决子问题,如何合并子问题的解解决原问题,以及递归终止条件又是什么。

  3. 题目分析:这是一道较为直白的递归问题,首先在定义中我们就知道将问题分解,由F(N) = F(N - 1) + F(N - 2)可知,我们可以将问题分解为F(N-1) 与 F(N-2) 的求和,而要解决该问题,可以通过递归来不断解决,在合并时,我们通过加法完成对子问题的合并解决原问题,最后的终止条件就是在N == 1 || N == 2 时,从递推转推回归。

  4. 经过分析我们可以写出代码:

    int Fibonacci_sequence(int n) {// 终止条件if (n == 1 || n == 2)return 1;else// 分解 解决子问题 合并子问题return Fibonacci_sequence(n - 1) + Fibonacci_sequence(n - 2);
    }
    

二. 递归法的不足

在上述篇幅我们解决了斐波那契额的问题,可以当我们把 n 输入到 50 时,我们就会发现,我们的程序仿佛已经死掉了,其实这主要的原因是在于在递的过程中,因为上一步计算需要先完成下一步计算才可以,所以每个函数在函数栈帧中会占用相应的内存,并且不被释放,知道回归合并为止。比如在此处,输入 n = 50 后,会有大量的内存遗留,甚至导致栈溢出,出现假死。不仅如此,还会出现像重复计算,在函数栈帧的创建与销毁需要时间与空间等问题

总体来说,主要问题有:

  • 递归使用函数栈帧模拟循环的过程,栈帧的开辟和销毁都需要时间;

  • 在递归使用函数栈帧的过程中,如果可能无法及时的释放内存而导致占用内容空间,甚至还会导致栈溢出;

  • 可能会出现大量的重复计算,提高时间复杂度

三. 递归优化——尾递归

所谓的尾递归,我们先给出概念,就是当编译器检测到尾递归时,覆盖当前栈帧,而不是去建立一个新的栈帧,这样只需要占用一个函数栈帧空间,防止了内存的大量浪费。

为了解尾递归,我们需要用尾调用入手,掌握其基本原理,再运用于尾调用。

  1. 尾调用

    函数最后一步调用另外一个函数。

    如以下代码块就是一个典型的尾调用,在调用 test_3() 后,最后一步调用另外一个函数 test_2(),同时覆盖 test_3() 建立的栈帧,以此类推,调用的栈帧永远只有 1 条, 节省了空间。

    void test_1() {printf("test====>1\n");
    }
    void test_2() {printf("test====>2\n");return test_1();
    }
    void test_3() {printf("test====>3\n");return test_2();
    }
    

    还需要区分一下情况不是尾调用,首先 test_5( ) 是一个赋值操作,test_6( ) 是是需要保留我们的函数栈帧的,test_7( ) 并未返回是一个使程序崩溃的。

    int test_4() {return 1;
    }
    int test_5() {int ret = test_4();return ret;
    }int test_6() {return test_4() + 1;
    }void test_7(){test_7();
    }
    
  2. 尾调用优化

    函数最后一步,不需要保留函数帧,其中调用位置与内部变量不需要再被用到。只需要将最后一个函数执行之前return即可。

    void f(){.........return g();
    }
    
  3. 尾递归

    递归就是自己调用自己,尾递归就是自己在最后一步调用自己

    根据尾调用与递归的结合,我们可以使得递归不再出现占用过多内存的问题,防止栈溢出的发生,起到了优化的作用。

    我们要如何做到实现尾调用呢,首先要求函数其中调用位置与内部变量不需要再被用到,我们可以把这些中间量放到参数列表中,其次要求在最后一步调用自己,我们在像递归一样调用即可。

  4. 尾递归解决斐波那契问题

    解决斐波那契问题,n表示递归次数,终止条件是 n=1 或者 n=2 的情况,此时返回 F( n -1) + F( n -2 ) …… F( 2 ) + F ( 1 ) 即可,否则,我们就需要使用尾递归,其中中间值放在参数中,使用 temp1 与 temp2 来代替,其中temp2始终记录每次递归的最后结果。

    int Fibonacci_sequence_better(int n,int temp1,int temp2) {if (n == 1 || n == 2)return temp2;elsereturn Fibonacci_sequence_better(n - 1, temp2, temp1+temp2);	
    }
    
  5. 尾递归实例——阶乘

    解决阶乘的尾递归问题,我们也是采取相同的思路,n表示递归次数,在n==1 时为终止条件,我们只需要返回 temp 算好的结果,否则,继续使用尾递归计算,其中参数就存放中间值,这样就可以覆盖栈帧,而不使得原栈帧返回。

    int factorial(int n, int temp) {if (n == 1)return temp;elsereturn factorial(n - 1, temp * n);
    }
    

四. 总结尾递归书写方法

尾递归从本质上来说就是覆盖原有的栈帧空间,防止内存过度占用,而实现的主要条件就是要求函数其中调用位置与内部变量不需要再被用到,为此我们所想出的解决办法就是将这些数据作为中间变量出现,如此就可以防止原函数无法出栈的情况。最后既然是递归,仍旧需要调用自身,完成递归的过程。

为此作者通过上述例题进行进行一个简单的提炼,希望能帮助大家,同时尾递归的运用离不开理解与联系,所以大家必须要着手于实践之中。提炼内容如下:

//n:迭代次数
//temp1与temp2:将需要使用的局部变量通过参数的形式出现
int Fibonacci_sequence_better(int n,int temp1,int temp2) {//终止条件if (n == 1 || n == 2)// temp2 表示累计递归结果return temp2;else//递归调用自身return Fibonacci_sequence_better(n - 1, temp2, temp1+temp2);	
}

五. 总结

在介绍递归的文章中,作者写了两篇,其中介绍了自己如何去解决递归问题,然后在本篇中完成了对递归优化的拓展。博主认为本篇所讨论的只是扩展内容,不需过度深究,了解即可,更重要的是解决递归问题的方法。

最后对两篇文章进行概括:

  • 分析解决递归问题:分解问题、解决子问题、合并子问题得到原问题答案、终止条件;
  • 尾递归优化:将局部变量转换为中间参数,在最后调用自身;

递归问题的理解以及运用是需要练习的,所以在掌握理论知识后,仅需要进行大量练习,这样可以巩固所学,同时还可以有自己更新的理解。


补充:

  1. 代码将会放到: https://gitee.com/liu-hongtao-1/c–c–review.git ,欢迎查看!
  2. 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!
    所以在掌握理论知识后,仅需要进行大量练习,这样可以巩固所学,同时还可以有自己更新的理解。

http://chatgpt.dhexx.cn/article/y0FSAi2p.shtml

相关文章

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

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

微服架构的论述?

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

微服务架构是什么?

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

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;各种历史包…

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

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

分布式/微服务必配APM系统,SkyWalking让你不迷路

前言 如今分布式、微服务盛行&#xff0c;面对拆分服务比较多的系统&#xff0c;如果线上出现异常&#xff0c;需要快速定位到异常服务节点&#xff0c;假如还用传统的方式排查肯定效率是极低的&#xff0c;因为服务之间的各种通信会让定位更加繁琐&#xff1b;所以就急需一个…

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

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

【微服】单体、SOA、微服务

单体架构 将所有的功能都集中在一个模块中&#xff08;WAR包&#xff09;开发、部署、迭代&#xff0c;牵一发而动全身&#xff0c;局部低效率拖垮整个服务。 SOA 按服务对项目拆分&#xff0c;通过对外提供接口的方式提供服务&#xff0c;缓解了单体的单服务低效率拖垮整个服…

微服架构基础设施环境平台搭建 -(三)Docker+Kubernetes集群搭建

微服架构基础设施环境平台搭建 -&#xff08;三&#xff09;DockerKubernetes集群搭建 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动构建、编译、测试…

微服架构基础设施环境平台搭建 -(二)Docker私有仓库Harbor服务搭建

微服架构基础设施环境平台搭建 -&#xff08;二&#xff09;Docker私有仓库Harbor服务搭建 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动构建、编译、…

SpringCloud微服架构

微服务架构 1&#xff09;单体应用架构 高效开发&#xff1a;项目前期开发节奏快&#xff0c;团队成员少的时候能够快替代 架构简单&#xff1a;mvc架构&#xff0c;只需要借助Ide开发&#xff0c;调试即可 易于测试&#xff1a;只要通过单元测试或者浏览器完成 易于部署&…

微服架构

首先我们看看为什么要考虑使用微服务。 开发单体式应用 假设你正准备开发一款与Uber和Hailo竞争的出租车调度软件&#xff0c;经过初步会议和需求分析&#xff0c;你可能会手动或者使用基于Rails、Spring Boot、Play或者Maven的生成器开始这个新项目&#xff0c;它的六边形架构…

微服架构基础设施环境平台搭建 -(一)基础环境准备

微服架构基础设施环境平台搭建 -&#xff08;一&#xff09;基础环境准备 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动构建、编译、测试、发布的自动…