3.4 栈与递归

3.4.1 采用递归算法解决问题 

3.4 栈与递归的关系

栈和递归之间有着紧密的关系,特别是在算法和程序设计中。栈作为一种数据结构,可以有效地支持递归算法的实现。本节我们将详细讨论栈在递归算法中的作用及其在程序设计中的重要性。

1. 递归算法的基本概念

  • 定义和特点
    • 递归是一种算法结构或编程技巧,其中函数直接或间接地调用自身来解决更小的问题实例。
    • 递归算法通常包含基本情况(终止条件)和递归步骤(自我调用来解决更小的问题)。

2. 栈在递归中的角色

  • 概述

    • 在实现递归时,栈被用作一个辅助数据结构,用于保存函数调用的信息,如返回地址和局部变量。
    • 栈的后进先出(LIFO)特性确保了函数调用的正确执行和返回。
  • 详细描述

    • 保存返回地址:在函数调用时,返回地址被推送到栈中,以便函数可以在执行完毕后返回到正确的位置。
    • 存储局部变量:函数的局部变量也存储在栈中,保证了在每次函数调用时,变量的正确初始化和使用。

3. 递归算法的设计和实现

  • 基本步骤

    • 确定基本情况:定义递归算法的终止条件。
    • 递归表达式:描述如何通过解决更小的问题来解决当前问题。
  • 示例代码

    #include <stdio.h>int factorial(int n) {if (n == 0) {return 1; // 基本情况,0的阶乘为1} else {return n * factorial(n - 1); // 递归表达式,n的阶乘是n乘以n-1的阶乘}
    }int main() {int number = 5;printf("The factorial of %d is %d\n", number, factorial(number));return 0;
    }
    

4. 栈和递归在程序设计中的应用

  • 实例分析
    • 在计算机科学中,栈和递归常用于解决各种问题,如数据结构操作(如树的遍历)、图形算法(如分治算法)和动态编程等。
    • 通过理解栈和递归的关系,程序员可以更好地设计和实现高效的算法。

注意事项

  • 递归算法需要仔细设计基本情况和递归步骤,以避免无限递归和堆栈溢出错误。
  • 递归虽然可以使代码更简洁和优雅,但也可能导致效率降低和内存使用增加,因此应谨慎使用。

 

 

3.4.2 递归的过程与递归工作栈 

3.4.2 递归过程与递归工作栈

在这段文本中,对递归过程和递归工作栈的概念和运行机制进行了详细阐述。现在我将帮你逐点分析这些内容:

递归函数的运行机制

  1. 函数调用机制: 在程序中,函数可以调用其他函数,而递归则是函数调用自身。在函数调用中,调用函数和被调用函数的信息交换和链接是通过栈来实现的。

  2. 函数调用和返回的过程

    • 调用前需完成:
      1. 传递实参和返回地址给被调用函数。
      2. 为被调用函数的局部变量分配存储区。
      3. 控制转移到被调用函数。
    • 返回前需完成:
      1. 保存被调用函数的计算结果。
      2. 释放被调用函数的数据区。
      3. 根据保存的返回地址将控制转移到调用函数。
  3. 递归函数的层次概念: 在递归函数中,每次调用都会形成一个新的层次。这些层次和调用的顺序密切相关,可以通过递归工作栈来保持跟踪。

递归工作栈

  1. 递归工作栈的定义: 递归工作栈用于在递归过程中存储每一层递归所需的信息(如实参、局部变量和返回地址)。每次进入一个新的递归层次时,都会生成一个新的工作记录(或称活动记录)并压入栈顶。每次退出一个递归层次时,则从栈顶弹出一个工作记录。

  2. 递归函数的运行示例: 以下是一个简化的阶乘函数递归调用过程的示例代码,解释了在递归过程中栈的运行情况和活动记录的使用:

    #include <stdio.h>long Fact(long n) {long temp;if (n <= 0) return 1;else {temp = n * Fact(n - 1);return temp;}
    }int main() {long n = 4;long result = Fact(n);printf("The factorial of %ld is: %ld\n", n, result);return 0;
    }
    

    解析

    • main 函数调用 Fact(4),进入第一层递归。
    • Fact(4) 调用 Fact(3),进入第二层递归,这一过程继续,直到调用 Fact(0),此时达到递归基条件,返回1。
    • 接着开始从栈顶逐步弹出每层递归的工作记录,计算并返回结果到上一层,直到最终返回到 main 函数。

结论

通过这段文本,我们可以更好地理解递归函数的运行机制和递归工作栈的工作原理。

 

 3.4.3 递归算法的效率分析

这段文本对递归算法的效率进行了深入的分析,涉及时间复杂度和空间复杂度的计算。下面我会针对这些内容提供详细的解析:

### 3.4.3 递归算法的效率分析

#### 1. 时间复杂度的分析

在递归算法的分析中,时间复杂度通常通过建立递归方程来计算。递归方程的求解可以通过多种方法,其中一种是迭代法。迭代法通过将递归方程不断展开,最终将其转换为一个非递归的和式,然后估算这个和式来得到方程的解。

例如,我们可以使用迭代法来分析阶乘函数`Fact(n)`的时间复杂度:

int Fact(int n) {if (n == 0) return 1;else return n * Fact(n - 1);}

- **递归方程建立:**
  递归函数`Fact(n)`的执行时间可表示为`T(n)`,其中

  其中D和C是常数,代表各个操作的执行时间。

- **递归方程解析:**
  通过迭代法我们可以将`T(n)`不断展开,直至找到一个模式或达到一个已知条件。按照文本的描述,我们可以得到`T(n) = nC + D`,因此时间复杂度为O(n)。

#### 2. 空间复杂度的分析

在递归算法中,系统使用“递归工作栈”来存储每层递归所需的信息,这是分析递归算法空间复杂度的关键。

- **递归工作栈的概念:**
  递归工作栈是用于存储每次递归调用所需的信息(如参数、局部变量和返回地址等)的数据结构。

- **空间复杂度计算:**
  对于递归算法,空间复杂度通常可以表示为

,其中是递归工作栈中工作记录数量与问题规模n的函数关系。例如,在阶乘问题、斐波那契数列问题和汉诺塔问题中,空间复杂度都是O(n),因为工作栈中的记录数量与n成正比。

 

3.4.4 利用栈将递归转换为非递归的方法 

3.4.4 利用栈将递归转换为非递归的方法

在解析递归算法时,我们可以注意到它依赖于系统提供的隐式栈来执行。根据递归算法执行过程中的递归工作栈的状态变化,我们可以编写相应的非递归算法。以下是消除递归过程的具体步骤:

(1) 创建工作栈

创建一个工作栈来存储递归工作记录,包括实参(实际参数)、返回地址和局部变量等。

(2) 初始化非递归调用

在非递归调用的入口(被调用程序的开始处),初始化实参和返回地址。由于递归程序不能作为主程序,我们可以假设它最初是由某个调用程序调用的。

(3) 模拟递归分解过程

当不满足递归结束条件时,逐层递归,将实参、返回地址和局部变量压入栈中。这个过程可以通过循环语句来实现,从而模拟递归分解的过程。

(4) 设置递归退出条件

当递归结束条件被满足时,将给定常数作为当前函数值,标记递归的结束。

(5) 模拟递归求值过程

在栈不为空的情况下,重复执行以下步骤:弹出栈顶记录,根据记录中的返回地址执行特定的操作,即逐层计算当前函数值,直到栈为空为止。

结论

通过以上步骤,我们可以将任何递归算法转换为非递归算法。但是,这样改写后的非递归算法通常会失去原有的结构清晰度和可读性,有时还需进一步优化。更多具体实例可以参见本文后续的5和5.1节,其中包含二叉树中序遍历的非递归算法示例。

 

 

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

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

相关文章

Swift学习内容精选(一)

Swift 可选(Optionals)类型 Swift 的可选&#xff08;Optional&#xff09;类型&#xff0c;用于处理值缺失的情况。可选表示"那儿有一个值&#xff0c;并且它等于 x "或者"那儿没有值"。 Swfit语言定义后缀&#xff1f;作为命名类型Optional的简写&…

uni-app--》基于小程序开发的电商平台项目实战(一)

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

vue响应式详解

1. 响应式的定义 我们都知道&#xff0c;vue是基于javascript的&#xff0c;那我们使用一段javascript代码来描述响应式 let a 1,b 1,c; c a b; console.log(c) // 输出 2 // 改变 a的值 a 3; // 重新给c赋值 即把 c a b; 再执行一遍c的值才能变为 4 // c a b; // …

小白也可以玩转CMake之常用必备

目录 1.设置编译器flags2.设置源文件属性3.链接器标志4.Debug与Release包 今天&#xff0c;分享一篇工作中经常用到的一些CMake命令&#xff0c;看完就学会了哦&#xff0c;更多CMake与C内容也期待加入星球与我一起学习呀~ 1.设置编译器flags 例如&#xff1a;设置C标准&#x…

C高级shell脚本

#!/bin/bash function fun() {sum0i0b($*)while [ $i -lt ${#b[*]} ]do((sum ${b[i]}))((i))doneecho $sum }read -p "请输入数组" -a a fun ${a[*]}function fun2() {aid -ubid -gecho $a $b } p(fun2) uid${p[0]} pid${p[1]} echo $uid $pidXMind

飞行动力学 - 第20节-横向静稳定性 之 基础点摘要

飞行动力学 - 第20节-横向静稳定性 之 基础点摘要 1. 横向静稳定性2. 横向静稳定准则3. 横向静稳定性的组成4. 参考资料 1. 横向静稳定性 2. 横向静稳定准则 对于横向静稳定性飞机&#xff0c;右滚转扰动会产生正侧滑&#xff0c;飞机产生左滚恢复力矩(负)&#xff0c;即 Δ …

java 身份证号码验证

需要编号文件 编号文件部分内容如下 11:北京市 1101:市辖区 110101:东城区 110102:西城区 110105:朝阳区 110106:丰台区 110107:石景山区 110108:海淀区 ...... 编号文件内容比较多 csdn点击 下载地址 java代码如下 import java.io.*; import java.text.ParseException; im…

github 创建自己的分支 并下载代码

github创建自己的分支 并下载代码 目录概述需求&#xff1a; 设计思路实现思路分析1.进入到master分支&#xff0c;git checkout master;2.master-slave的个人远程仓库3.爬虫调度器4.建立本地分支与个人远程分支之间的联系5.master 拓展实现 参考资料和推荐阅读 Survive by day…

golang面试题:reflect(反射包)如何获取字段tag​?为什么json包不能导出私有变量的tag?

问题 json包里使用的时候&#xff0c;会结构体里的字段边上加tag&#xff0c;有没有什么办法可以获取到这个tag的内容呢&#xff1f; 举例 tag信息可以通过反射&#xff08;reflect包&#xff09;内的方法获取&#xff0c;通过一个例子加深理解。 package mainimport (&quo…

Linux 6.6 初步支持AMD 新一代 Zen 5 处理器

AMD 下一代 Zen 5 CPU 现已开始为 Linux 6.6 支持提交相关代码&#xff0c;最新补丁包括提供温度监控和 EDAC 报告等。 最新的 Linux 6.6 代码中已经加入了包括支持硬件监视器温度监控和 EDAC 报告的补丁。此外&#xff0c;新版本还加入了 x86 / misc 补丁&#xff0c;Phoronix…

初出茅庐的小李博客之根据编译时间生成软件版本号

为什么要软件版本号呢&#xff1f; 生成软件版本号是在软件开发和维护过程中非常重要的一项任务&#xff0c;它有很多意义和好处&#xff0c;同时也有多种常见的方法。 标识和追踪&#xff1a;软件版本号是唯一的标识符&#xff0c;用于区分不同版本的软件。这有助于开发人员和…

华为云云服务器云耀L实例评测 | 华为云云服务器实例新品全面解析

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

CATIA Composer软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 CATIA&#xff08;Computer-Aided Three-dimensional Interactive Application&#xff09;是一款由法国达索系统公司开发的三维计算机辅助设计&#xff08;CAD&#xff09;软件。它是一种全面的产品开发解决方案&#xff0c;广泛…

13-JVM调优实战-3

上一篇&#xff1a;12-JVM调优实战-2 今天来介绍一款阿里巴巴的调优工具。 Arthas详解 Arthas 是 Alibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6&#xff0c; 采用命令行交互模式&#xff0c;可以方便的定位和诊断线上程序运行问题。Arthas 官方文档十分详细&am…

约瑟夫环(循环列表实现)

约瑟夫&#xff08;Joseph&#xff09;问题的一种描述是&#xff1a;编号为1&#xff0c;2&#xff0c;3&#xff0c;…&#xff0c;n的n个人按顺时针方向围坐一圈。每人持有一个密码&#xff08;正整数&#xff09;。一开始任选一个正整数作为报数上限值m&#xff0c;从第一个…

docker使用(二)提交到dockerhub springboot制作镜像

docker使用&#xff08;二&#xff09; dockerhub创建账号创建存储库成功&#xff01;开始推送获取image名 提交成功SpringBoot项目制作Dockerfile镜像部署打jar包 dockerhub创建账号 &#xff08;自认为可以理解为github一类的东西&#xff09; 单击创建存储库按钮。 设定存…

uniapp 小程序 全局弹窗 每个需要使用的页面都不用再引用

文章目录 创建组件在项目的根目录下的vue.config.vue中配置页面中使用 使用全局组件&#xff0c;先声明全局组件 与普通的组件声明不同之处在于 1&#xff1a;目录形式 2&#xff1a;声明引用方式 创建组件 在components目录中创建组件目录/组件vue&#xff0c;如下 注意需要同…

面向Ai设计的Mojo编程语言支持下载,当前只有Linux系统版本

据了解&#xff0c;Mojo是Modular AI公司开发的专门面向AI设计的编程语言&#xff0c;号称比Python快68000倍。 Mojo现已开放本地下载运行&#xff0c;除了编译器之外&#xff0c;Mojo SDK还包括一整套开发者和IDE工具&#xff0c;并用来构建和迭代 Mojo应用。 公司方面表示&…

二.RocketMQ基础概念及名词说明

RocketMQ基础概念及名词说明 一&#xff1a;RocketMQ基本概念1.消息&#xff08;Message&#xff09;2.生产者(Producer)3.消费者(Consumer)4.分组(Group)&#xff1a;4.主题&#xff08;Topic&#xff09;5.标签&#xff08;Tag&#xff09;6.队列&#xff08;Queue&#xff0…

FFMPEG视频压缩与Python使用方法

一、简介 FFMPEG 是一个完整的&#xff0c;跨平台的解决方案&#xff0c;记录&#xff0c;转换和流音频和视频。 官网&#xff1a;https://ffmpeg.org/ 二、安装 1、Linux&#xff1a; sudo apt install ffmpeg 2、Mac: brew install ffmpeg 3、Windows: 下载文件&#…