【算法】算法学习三:递归算法 & 栈

2023-11-06

一、递归的含义

递归算法是一种解决问题的方法,其中函数在执行过程中调用自身。它通过将一个大问题拆分成一个或多个相似的子问题,并逐步解决这些子问题来解决整个问题。递归算法通常包含两个关键组成部分:基本情况和递归调用。

基本情况是指可以直接求解或返回结果的简单情况。在递归算法中,当问题达到基本情况时,递归停止,避免无限循环。基本情况是算法中的出口条件。

递归调用是指函数在执行过程中调用自身来解决更小规模的子问题。通过不断调用自身,并将问题规模减小到基本情况的范围内,递归算法能够逐步解决原始问题。每次递归调用都在一个更小的子问题上进行操作,直到最终达到基本情况。

递归算法的实现通常包括以下几个步骤:

  1. 确定基本情况:确定问题的边界条件,即可以直接求解的简单情况。
  2. 定义递归函数:编写一个函数,其中包含对自身的调用来解决更小规模的子问题。
  3. 缩小问题规模:在递归函数中,通过传递更小规模的子问题来逐步缩小原始问题的规模。
  4. 递归调用:在递归函数内部调用自身来解决子问题。
  5. 结合子问题的解:利用子问题的解来解决原始问题。

需要注意的是,递归算法必须确保在每次递归调用时问题规模都能够缩小,否则可能导致无限递归和栈溢出等问题。此外,递归算法的效率可能较低,因为它涉及多次函数调用和重复计算。

递归算法在许多问题上都能提供简洁而优雅的解决方案,如树和图的遍历、排列组合、动态规划等。在编写递归算法时,正确定义基本情况和递归调用非常重要,以确保算法能够正确地终止并给出正确的结果。

二、基线条件和递归条件

对于递归算法,有两个关键概念需要考虑:基线条件(Base Case)和递归条件(Recursive Case)。

基线条件是指在递归算法中确定的一个或多个停止递归的条件。当满足基线条件时,递归将不再进行,而是直接返回一个结果或执行其他操作。基线条件通常是问题的最小规模或最简单情况。

递归条件是指在递归算法中定义的条件,用于决定是否需要进行递归调用。当问题的规模还没有达到基线条件时,递归条件将触发递归调用,将问题拆分为更小的子问题,并继续递归求解。

基线条件和递归条件共同构成了递归算法的终止条件和递归调用条件。

以下是一个计算阶乘的递归算法的示例:

def factorial(n):
    # 基线条件
    if n == 0:
        return 1
    
    # 递归条件
    else:
        return n * factorial(n-1)

在这个例子中,基线条件是当输入的数字 n 等于 0 时,直接返回结果 1。递归条件是当 n 不等于 0 时,通过调用自身传入较小规模的子问题 n-1,然后将子问题的结果乘以 n,最终得到整个问题的解。

基线条件确保递归能够终止,而递归条件则控制递归的执行过程,将问题逐步缩小直至达到基线条件。

在编写递归算法时,正确定义基线条件和递归条件非常重要。如果基线条件不正确或递归条件不满足,可能导致无限递归或无法获得正确的结果。同时,还要确保问题规模在每次递归调用时都能够减小,以避免无限递归和栈溢出等问题。

三、栈

3.1 什么是栈

栈(Stack)是一种常见的数据结构,它按照后进先出(Last In, First Out,LIFO)的原则工作。它类似于现实生活中的一叠盘子,只能在顶部进行插入和移除操作。

栈具有以下特点:

  1. 后进先出:最后插入的元素将首先被移除,而最先插入的元素将成为栈中的底部元素,只有在底部的元素才能被最后移除。
  2. 插入和移除操作:栈有两个主要操作,即入栈(Push)和出栈(Pop)。入栈将元素添加到栈的顶部,出栈将栈顶的元素移除。
  3. 只能访问栈顶元素:除了入栈和出栈操作外,栈还提供了一种访问栈顶元素的方法,通常称为Peek或Top操作。通过Peek操作,可以查看栈顶的元素而不对其进行移除。

栈的实现通常基于数组或链表。使用数组实现的栈被称为顺序栈,而使用链表实现的栈被称为链式栈。

栈在计算机科学中有广泛的应用,例如:

  1. 函数调用:在函数调用期间,计算机使用栈来存储函数的调用信息。每当一个函数被调用,其参数、返回地址和局部变量等信息将被推入栈中。当函数执行完成后,这些信息将被弹出栈。
  2. 表达式求值:在表达式求值过程中,栈可用于存储运算符和操作数。通过使用栈,可以按照正确的顺序计算表达式中的操作。
  3. 浏览器历史记录:浏览器使用栈来存储用户访问的不同网页,每当用户点击“后退”按钮时,最后访问的网页将被弹出栈。
  4. 撤销操作:许多编辑器和应用程序使用栈来实现撤销操作。每当用户执行一个操作时,相关的信息将被推入栈中,当用户执行撤销操作时,最后的操作将被弹出栈,以返回到之前的状态。

栈的简单性和实用性使其成为解决各种问题的重要工具。它提供了一种有序且高效的数据存储和访问方式,能够满足许多算法和应用程序的需求。

3.2 调用栈

调用栈(Call Stack)是一种用于跟踪程序中函数调用和返回的机制。它是一个栈结构,用于存储函数调用时的相关信息,包括函数的参数、局部变量和返回地址等。调用栈的操作基于后进先出(LIFO)原则。

当一个函数被调用时,它的相关信息会被推入调用栈中,形成一个栈帧(Stack Frame)或称为活动记录(Activation Record)。栈帧包含了函数的参数、局部变量以及调用该函数后需要返回的地址。然后,程序执行被调用的函数。

如果被调用的函数内部再次调用其他函数,新的栈帧将被创建并推入调用栈中。这样,栈中的顶部栈帧始终代表当前正在执行的函数。

当函数执行完成后,对应的栈帧将从调用栈中弹出,程序将返回到调用该函数的位置继续执行。被弹出的栈帧被丢弃,其内存空间可以被重新利用。

调用栈在程序执行期间起着重要的作用,它管理了函数调用的顺序和相关信息,确保函数的正确执行和返回。如果函数调用的嵌套层级很深或者递归调用的次数很多,调用栈可能会变得很大,有时会导致栈溢出的错误。

调用栈也可以用于调试程序。通过查看调用栈中的栈帧信息,可以了解程序执行过程中函数的调用顺序和参数值,有助于定位错误和理解代码的执行流程。

总而言之,调用栈是一种用于管理函数调用和返回的数据结构,它提供了一种跟踪程序执行流程的方式,对于函数的嵌套调用和程序的执行顺序至关重要。

def greet2(name):
    print("How are you? " + name + " !")

def bye():
    print("OK, bye!")

def greet(name):
    print("Hello, " + name + " !")
    greet2('Mike')
    bye()
    print('Bye!')

greet('Tom')

输出结果为:

Hello, Tom !
How are you? Mike !
OK, bye!
Bye!

调用栈的执行顺序如下:

  1. 主程序开始执行,调用了 greet(‘Tom’)。
  2. greet 函数被推入调用栈。
  3. 在 greet 函数内部,调用了 greet2(‘Mike’)。
  4. greet2 函数被推入调用栈。
  5. greet2 函数执行完成后,从调用栈中弹出。
  6. 控制权返回到 greet 函数,继续执行。
  7. 在 greet 函数内部,调用了 bye()。
  8. bye 函数被推入调用栈。
  9. bye 函数执行完成后,从调用栈中弹出。
  10. 控制权返回到 greet 函数,继续执行。
  11. greet 函数执行完成后,从调用栈中弹出。
  12. 主程序执行结束。

通过调用栈,我们可以跟踪和管理函数的嵌套调用和返回过程,确保函数按照正确的顺序执行。调用栈在函数调用和返回的过程中起到了重要的作用。

3.3 递归调用栈

递归调用栈是指在函数内部调用自身的过程中形成的调用栈结构。当一个函数在执行过程中再次调用自身,新的函数调用会被推入调用栈,形成一系列嵌套的函数调用。

让我们以一个简单的递归函数示例来说明递归调用栈的工作原理:

def countdown(n):
    if n <= 0:
        print("Go!")
    else:
        print(n)
        countdown(n - 1)

countdown(5)

在这个例子中,我们定义了一个名为 countdown 的递归函数。该函数接收一个参数 n,并按照从 n 倒数到 1 的顺序进行打印,直到达到基线条件 n <= 0,然后打印 “Go!”。

当我们调用 countdown(5) 时,以下是递归调用栈的执行过程:

  1. countdown(5) 被推入调用栈。
  2. countdown(5) 执行,并打印 5。
  3. countdown(4) 被推入调用栈。
  4. countdown(4) 执行,并打印 4。
  5. countdown(3) 被推入调用栈。
  6. countdown(3) 执行,并打印 3。
  7. countdown(2) 被推入调用栈。
  8. countdown(2) 执行,并打印 2。
  9. countdown(1) 被推入调用栈。
  10. countdown(1) 执行,并打印 1。
  11. countdown(0) 被推入调用栈。
  12. countdown(0) 执行,满足基线条件,打印 “Go!”。
  13. countdown(0) 从调用栈中弹出。
  14. 控制权返回到上一层的 countdown(1)。
  15. countdown(1) 从调用栈中弹出。
  16. 控制权返回到上一层的 countdown(2)。
  17. 依此类推,直到 countdown(5) 从调用栈中弹出,程序执行结束。

递归调用栈在递归函数的执行过程中起到关键作用。它管理了递归函数的嵌套调用顺序和相关信息,确保函数按照正确的顺序被调用和返回。如果递归调用层级过深,调用栈可能会变得很大,有时会导致栈溢出的错误。因此,在编写递归函数时,需要注意控制递归的终止条件,以避免无限递归和栈溢出的问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【算法】算法学习三:递归算法 & 栈 的相关文章

随机推荐

  • 【Linux下Docker安装JupterLab】

    Linux下Docker安装JupterLab 拉取docker镜像 docker pull jupyter base notebook latest https jupyter docker stacks readthedocs io e
  • cicd 02--构建通用的CD流程

    cicd 02 构建通用的CD流程 1 介绍 2 CD 构建过程 2 1 参数配置说明 2 2 pipeline 脚本 2 3 测试流程 3 注意事项 4 说明 1 介绍 笔者在 cicd 01 构建通用的CI流程 中介绍了一个通用的doc
  • 简单分析 C 语言的 qsort() 源码

    简单分析 C 语言的 qsort 源码 stdlib h 是使用 C 语言需要引入的库 在系统文件下可以搜索到这个文件夹 在里面可以看到有一个 qsort 文件用编译器或者记事本打开就能看到里面的源码了 单从文件名看 qsort 采用的是快
  • unity ScriptableObject

    ScriptableObject代替单例 和单例一样 在内存是独一份的 是可以被不同的东西读取 需要一些工具链配合 结构是这样的 startEvent事件 gt EventListener事件监听 gt 事件数据Event 这样可以跨sce
  • Charles设置代理后,手机无法上网

    要抓手机app的包 手机配置好代理后 能连接到Charles 但是手机无法上网 原因 Charles开启了White list 解决方式 关闭White List Tools gt White List 实现charles抓取手机访问 ht
  • 解决“您一次只能安装一种 Adobe 产品”问题

    由于dreamweaver不慎升级导致不能用 故准备卸载了重新安装 可是卸载之后一直装不上 总是提示 您一次只能安装一种 Adobe 产品 用优化大师等软件卸载清除注册表信息都不行 搜索后发现一款很好的软件 Windows Installe
  • Python变量类型的强制转换

    当我们需要对数据的类型转换时 只需要将数据类型作为函数名即可 下面给出的函数可以执行数据类型之间的转换 函数返回一个新的对象 表示转换的值 函数格式 使用示例 描述 int x base int 8 可以转换的包括String类型和其他数字
  • 《C++API设计》阅读笔记1

    1 API简介 API Application Programing Interface 提供了对某个问题的抽象 以及客户与解决改问题的软件组件之间进行交互的方式 组件本身通常以软件类库形式分发 它们可以在多个应用程序中使用 概括说 API
  • ROS机器人构建和深度学习应用

    机器人操作系统是机器人研究和公司建模 模拟和原型机器人使用最广泛的软件框架之一 将您的 ROS 知识应用于实际机器人技术比人们意识到的要困难得多 但是这个标题将立即为您提供创建自己的机器人技术所需的一切 包含超过 14 个 ROS 机器人项
  • python中input()函数详解

    1 input 函数赋值后数据在python内部的类型 if name main a input print type a b input print type b c a b print c print type c 输入及输出 从结果可
  • 解决Anaconda环境未激活的warning

    在cmd内键入python之后会报Warning 显示Anaconda环境未激活 使用如下命令激活 conda activate base 这里base指环境名 默认为base 查看环境名可以用如下命令 conda info envs
  • Oracle常见问题定位方法

    Oracle在安装时无法正常显示出安装界面 现象 在vnc的界面中 安装时提示 Can t connect to X11 window server using 1 0 as the value of the DISPLAY variabl
  • 使用注解开发springmvc

    第三步 pom xml文件引入相关依赖 主要有Spring框架核心库 Spring MVC servlet JSTL等 第四步 配置web xml 映射路径 不要为 会404 第六步 创建Controller RequestMapping
  • 某宝滑块ua特征研究

    从137版本开始某宝新加了x 82类型滑块 和之前无感或者滑动验证js 类似 不过浏览器特征检测 反调试干扰都增加了不少 变得更有难度 下面稍微讲下研究过程 首先处理大量三目运算符 这个没啥好办法 博主用ast处理的 类似下面这种 单步调试
  • 贪心算法求解TSP问题(python)

    这里使用贪心算法求解TSP问题的python版本 dist 为距离矩阵 start index 为起始位置 def tsp quick dist list start index int sum distance seq result n
  • 用C++做一颗会跳动的爱心

    先来看看效果 程序描述 程序先以较慢的速度画一个大爱心 之后跳动的心其实从视觉上看就是一大一小两个心相互切换 但是要调整一下大小爱心变化时的时间间隔 代码主要是通过设置两个函数 利用cls来清屏 重复打印大心和小心 并设置颜色为红色 详细代
  • php公众号获取code,微信公众号获取code

    methods getCode 非静默授权 第一次有弹框 this code var local window location href 获取页面url var appid wx65adcf075369 this code this ge
  • 使用LFM(Latent factor model)隐语义模型进行Top-N推荐

    最近在拜读项亮博士的 推荐系统实践 系统的学习一下推荐系统的相关知识 今天学习了其中的隐语义模型在Top N推荐中的应用 在此做一个总结 隐语义模型LFM和LSI LDA Topic Model其实都属于隐含语义分析技术 是一类概念 他们在
  • Windows 下如何安装配置Snort视频教程

    Windows 下如何安装配置Snort视频教程 第一步 http www tudou com programs view UUbIQCng360 第二部 http www tudou com programs view NqcPETQk2
  • 【算法】算法学习三:递归算法 & 栈

    文章目录 一 递归的含义 二 基线条件和递归条件 三 栈 3 1 什么是栈 3 2 调用栈 3 3 递归调用栈 一 递归的含义 递归算法是一种解决问题的方法 其中函数在执行过程中调用自身 它通过将一个大问题拆分成一个或多个相似的子问题 并逐