算法学习:递归

在这里插入图片描述

🔥 个人主页:空白诗

在这里插入图片描述

文章目录

    • 一、引言
    • 二、什么是递归?
    • 三、两大基本要素
      • 🏁 基线条件(Base Case)
      • 🔁 递归条件(Recursive Case)
      • 📃 代码示例:计算斐波那契数列
      • 💻 代码示例:计算阶乘
    • 四、递归的注意事项
      • 1. 栈溢出风险:监控递归深度
      • 2. 效率考量:平衡计算资源
      • 3. 逻辑清晰性:确保易于理解和维护
    • 五、递归的好处
      • 1. 计算斐波那契数列(While循环实现)
      • 2. 计算阶乘(While循环实现)
    • 六、总结


一、引言

递归,这个在编程世界中既神奇又强大的概念,常常让初学者既着迷又困惑。想象一下,一个函数能够自我调用,层层深入,最终解决复杂问题,就像俄罗斯套娃一样,一层套着一层,直到达到最核心的解答。本文将带你一步步走进递归的世界,用JavaScript这把钥匙,解锁递归之门的秘密!🚪


二、什么是递归?

在这里插入图片描述

递归,简单来说,就是一个函数在其定义中直接或间接地调用自身的过程。它通常用于解决那些可以通过分解为相似子问题的问题,比如计算阶乘、遍历树形结构、寻找斐波那契数列等。


三、两大基本要素

🏁 基线条件(Base Case)

  • 定义: 基线条件是递归过程的停靠点,是递归函数不再调用自身的条件。
  • 作用: 确保递归不会无限进行,是递归函数能够最终返回结果的关键。
  • 特点: 应该是最简单的情况,可以直接给出答案,无需进一步递归。

🔁 递归条件(Recursive Case)

  • 定义: 递归条件描述了如何将原问题分解成较小的子问题,并继续调用自身来解决这些子问题。
  • 作用: 实现问题的逐步简化,直至达到基线条件。
  • 特点: 每次递归调用都应使问题规模减小,趋近于基线条件。

📃 代码示例:计算斐波那契数列

斐波那契数列是递归的经典案例,其中每个数字是前两个数字的和,序列从0和1开始。

斐波那契数列(Fibonacci sequence)是一个非常著名的数列,在数学上有着悠久的历史和广泛的应用。这个数列以其发现者,意大利数学家列昂纳多·斐波那契(Leonardo Fibonacci)的名字命名。斐波那契数列最初是斐波那契在《算盘书》(Liber Abaci)中以兔子繁殖的问题作为例子引入的,因此有时也被称为“兔子数列”。

斐波那契数列的定义是这样的:数列的前两项通常是1(有些定义中第一项为0,第二项为1),之后的每一项都是前两项之和。用数学表达式表示就是:

[ F(0)=0, , F(1)=1, , F(n)=F(n-1)+F(n-2) , \text{对于} , n \geq 2 ]

按照这个规则,数列的前几项是:

[ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, \ldots ]

斐波那契数列在自然界和艺术中都能找到其身影,比如植物的分支模式、花瓣排列、动物的生长序列等,都与斐波那契数列紧密相关。此外,它还在计算机科学、密码学、经济学、音乐理论等领域有着重要应用,并且与黄金分割比例(大约为1.61803398875)有着密切的数学联系。斐波那契数列的连续两项之比,随着项数的增加,趋近于黄金比例。

JavaScript实现

function fibonacci(n) {
  if (n <= 1) {  // 基线条件:n为0或1时,直接返回n
    return n;
  } else {  // 递归条件:n的值由前两个斐波那契数相加得到
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

console.log(fibonacci(10)); // 输出: 55
  • 当调用fibonacci(10)时,它不满足基线条件,因此进入递归条件。
  • fibonacci(10)依赖于fibonacci(9)fibonacci(8)的结果。
  • 这个过程会一直持续,直到递归到fibonacci(0)fibonacci(1),这两个是基线条件,直接返回0和1。
  • 之后,每个递归层开始回溯,计算出各自的结果,最终fibonacci(10)得出55。

💻 代码示例:计算阶乘

阶乘(Factorial),一个在数学领域内同样举足轻重的概念,承载着深厚的理论意义和广泛的实际应用。阶乘源自乘法原理的延伸,其符号表示为n!,读作“n阶乘”。阶乘的提出,虽不如斐波那契数列那样具有传奇色彩的故事背景,却在数学分析、组合数学、概率论乃至计算机科学中占据着不可替代的地位。

阶乘的定义简洁而直观:对于任何非负整数n,

  • 如果n为0,则0! 定义为1,这是阶乘的基础约定,保证了递推关系的一致性。
  • 若n为正整数(n > 0),则n! = n × (n-1) × (n-2) × … × 2 × 1。

换言之,阶乘意味着将一个数与其以下的所有正整数相乘,形成一个累积的乘积。例如,

[ 5! = 5 × 4 × 3 × 2 × 1 = 120 ]

阶乘在数学计算中扮演着多重角色,如计算排列数、组合数时它是核心元素,同时也是多项式展开、级数求和、概率分布等领域不可或缺的工具。在计算机科学中,阶乘算法的实现,尤其是递归方法,常作为教学递归思想的经典案例,同时启发了对算法效率、栈空间管理等深入讨论。

function factorial(n) {
  // 基本情况:0的阶乘是1
  if (n === 0) {
    return 1; // 这就是我们的Base Case
  } else { // 递归情况:n的阶乘是n乘以n-1的阶乘
    return n * factorial(n - 1); // 这里调用了自身,是Recursive Case
  }
}

console.log(factorial(5)); // 输出: 120
  • factorial(5)被调用时,它检查n是否为0(基本情况),不是,则继续执行递归情况,即计算5 * factorial(4)
  • 接着factorial(4)调用自己,变成4 * factorial(3),以此类推,直到factorial(0)
  • 当到达factorial(0)时,这是基本情况,直接返回1,然后每个之前的调用开始返回其计算结果,最终factorial(5)计算完成,输出120。

四、递归的注意事项

递归编程作为算法设计的一项基本技能,其有效运用依赖于对几个核心要素的深刻理解和谨慎操作。以下是递归实践中必须留意的关键点:

1. 栈溢出风险:监控递归深度

问题描述示例:计算一个非常大的数的阶乘时,直接递归可能会导致栈溢出。

原始递归代码(存在栈溢出风险):

function factorial(n) {
    if (n === 0) return 1;
    return n * factorial(n - 1);
}

console.log(factorial(10000)); // 可能导致栈溢出

应对措施示例:设置递归深度限制

// 定义一个带有深度限制的阶乘计算函数
function factorialWithDepthLimit(n, depth = 0, maxDepth = 1000) { // 添加最大递归深度限制参数
    // 检查当前递归深度是否超过了设定的最大深度,如果是,则抛出错误
    if (depth > maxDepth) throw new Error("递归深度超出限制");
    // 递归终止条件:如果n等于0,返回1
    if (n === 0) return 1;
    // 递归调用:n乘以(n-1)的阶乘结果,并增加递归深度计数
    return n * factorialWithDepthLimit(n - 1, depth + 1, maxDepth);
}

// 尝试计算一个大数的阶乘,并捕获可能抛出的错误
try {
    console.log(factorialWithDepthLimit(10000)); // 尝试计算10000的阶乘
} catch (e) {
    // 如果发生错误(例如递归深度超限),打印错误信息
    console.error(e.message);
}

2. 效率考量:平衡计算资源

问题描述示例:计算斐波那契数列时,直接递归会有大量重复计算。

原始递归代码(效率低):

function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(30)); // 效率极低

这段代码定义了一个带有深度限制的阶乘计算函数factorialWithDepthLimit,旨在防止因递归调用过深而导致的栈溢出错误。函数接受三个参数:要计算阶乘的数n、当前递归深度depth(默认为0)、以及允许的最大递归深度maxDepth(默认为1000)。通过在递归过程中检查深度是否超过最大值,函数能够提前终止递归并抛出错误,从而保护程序免受栈溢出的影响。最后,通过try-catch结构调用该函数并妥善处理可能发生的错误。

优化策略示例:使用记忆化(缓存)

// 初始化一个Map用于存储已经计算过的斐波那契数,键为n,值为第n项斐波那契数
const memo = new Map();

// 定义一个使用记忆化的斐波那契函数
function fibonacciMemo(n) {
    // 如果之前已经计算过这个值,直接从memo中取出返回,避免重复计算
    if (memo.has(n)) return memo.get(n);
    
    let result;
    // 递归终止条件
    if (n <= 1) {
        result = n;
    } else {
        // 递归计算前两项斐波那契数之和
        result = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);
    }
    
    // 计算完成后,将结果存入memo中,供后续可能的使用
    memo.set(n, result);
    return result;
}

// 调用优化后的函数计算第30项斐波那契数,由于使用了记忆化技术,即使n较大也能高效计算
console.log(fibonacciMemo(30)); // 高效计算

这段代码通过引入一个memo(记忆)对象来存储已经计算过的斐波那契数,确保对于每一个n值,函数只会被调用一次,之后再次请求该值时直接从memo中查找而非重新计算,从而大大提高了计算效率,尤其是在计算较大的斐波那契数时效果显著。

3. 逻辑清晰性:确保易于理解和维护

挑战说明示例:复杂的递归逻辑如汉诺塔问题可能难于理解。

汉诺塔(Tower of Hanoi),又称为河内塔,是一个经典的递归问题和益智游戏,源自印度的一个古老传说。游戏由三根杆子(通常称为A、B、C)和一系列不同大小的盘子组成,最初所有盘子按照从大到小的顺序依次叠放在第一根杆子(A杆)上,且任何时候大盘子都不能放在小盘子之上。

游戏的目标是将所有盘子从起始杆(A杆)移动到目标杆(C杆),并且遵循以下规则:

  1. 每次只能移动一个盘子。
  2. 在任何时候,盘子都必须保持大盘在下,小盘在上的顺序。
  3. 只能通过借助第三根杆子(B杆,作为辅助)来完成移动,但最终所有盘子都必须移到C杆上。

汉诺塔问题可以用递归算法来解决,基本思路是将n个盘子的问题分解为两个子问题:先将上面的n-1个盘子借助C杆移动到B杆,然后将最下面的盘子直接移动到C杆,最后将B杆上的n-1个盘子移动到C杆上。这个过程不断地自我重复,直到只剩下一个盘子,直接移动即可。

汉诺塔不仅是一个有趣的智力游戏,也是计算机科学中教授递归思想的经典案例。通过解决汉诺塔问题,可以深入理解递归算法的设计和分析,以及递归如何通过将复杂问题分解为更简单实例来实现问题的解决。

在这里插入图片描述

原始递归代码(稍显复杂):

function hanoiTower(n, fromRod, toRod, auxRod) {
    if (n === 1) {
        console.log(`Move disk 1 from rod ${fromRod} to rod ${toRod}`);
    } else {
        hanoiTower(n - 1, fromRod, auxRod, toRod);
        console.log(`Move disk ${n} from rod ${fromRod} to rod ${toRod}`);
        hanoiTower(n - 1, auxRod, toRod, fromRod);
    }
}

hanoiTower(3, 'A', 'C', 'B');

解决办法示例:通过清晰的注释和模块化

/**
 * 使用auxRod作为辅助,将n个盘子从fromRod移动到toRod。
 * @param {number} n 碟子的数量
 * @param {string} fromRod 起始柱子
 * @param {string} toRod 目标柱子
 * @param {string} auxRod 辅助柱子
 */
function hanoiTower(n, fromRod, toRod, auxRod) {
    // 递归终止条件:如果只有一个盘子,直接从起始柱子移动到目标柱子
    if (n === 1) {
        console.log(`将磁盘1从杆${fromRod}移到杆${toRod}`);
    } else {
        // 将n-1个盘子从起始柱子借助目标柱子移动到辅助柱子
        hanoiTower(n - 1, fromRod, auxRod, toRod);
        
        // 移动最底下的第n个盘子(此时它是起始柱子上最大的盘子)到目标柱子
        console.log(`将磁盘${n}从杆${fromRod}移到杆${toRod}`);
        
        // 最后,将n-1个盘子从辅助柱子移动到目标柱子
        hanoiTower(n - 1, auxRod, toRod, fromRod);
    }
}

// 示例:使用清晰的注释和结构化调用来解决3个盘子的汉诺塔问题
hanoiTower(3, 'A', 'C', 'B'); // A为起始柱,C为目标柱,B为辅助柱

这段代码通过递归调用自身来逐步解决问题,展现了汉诺塔问题的分治策略。首先处理较小规模的问题(即将n-1个盘子借助某一柱子移动),然后处理当前规模的特殊部分(即移动最底下的一个盘子),最后再解决剩余的较小规模问题(将n-1个盘子从辅助柱移动到目标柱)。这样的递归逻辑清晰地反映了汉诺塔问题的解决步骤,易于理解和教学。


五、递归的好处

为了全面展示递归与传统循环(这里以forwhile为例)的区别,我们通过计算斐波那契数列和阶乘两个经典问题来对比递归与循环实现方式。

1. 计算斐波那契数列(While循环实现)

在上文中递归实现直接体现了斐波那契数列的定义,代码简洁。但存在重复计算和高时间复杂度的问题,对于大数容易造成栈溢出。

function fibonacciIterative(n) {
    let a = 0, b = 1, temp;
    while (n-- > 0) {
        temp = a;
        a = b;
        b = temp + b;
    }
    return a;
}

console.log(fibonacciIterative(10)); // 同样输出第10个斐波那契数

循环实现避免了递归的栈溢出风险,性能更高,特别是对于大数计算更为稳定,但可能不如递归直观。

2. 计算阶乘(While循环实现)

在上文中递归直接体现了阶乘的数学定义,代码易于理解,但对于极大数同样面临栈溢出的风险。

function factorialIterative(n) {
    let result = 1;
    while (n > 1) {
        result *= n;
        n--;
    }
    return result;
}

console.log(factorialIterative(5)); // 同样输出5的阶乘

而循环实现避免了递归可能导致的栈溢出问题,对于任何大小的输入都能稳定运行,但相比递归可能在表达问题本质方面显得不够直接。

递归的优点在于代码的简洁性和逻辑的直观性,它自然地符合某些问题的结构,比如树形结构遍历或分治算法。而循环,尤其是while循环,提供了更直接的控制流,对于性能敏感或深度大的情况更适用,能够避免递归带来的栈溢出问题。选择哪种方式取决于具体问题、性能要求以及对代码可读性的考量。

综上所述,递归编程是一把双刃剑,既能优雅地解决诸多问题,也暗含着潜在的挑战。通过合理设计、适时优化和清晰的编码实践,我们可以最大化递归的优势,同时有效规避其带来的风险。


六、总结

递归与循环(包括for循环和while循环)都是编程中实现重复逻辑的重要手段,它们各自有独特的应用场景和优缺点。通过对比斐波那契数列和阶乘问题的递归与循环实现,我们可以总结如下:

递归的优势:

  • 代码简洁: 递归能够以非常直观的方式直接反映问题的数学定义或逻辑结构,使得代码易于理解。
  • 自然表达复杂关系: 对于树形结构遍历、分治算法等问题,递归提供了一种自然且优雅的解决方案。
  • 逻辑清晰: 递归强调问题分解,将复杂任务分解为更小的相似子问题,有助于理解和设计算法。

递归的劣势:

  • 性能问题: 未优化的递归可能导致大量的重复计算和较高的时间复杂度。
  • 栈溢出风险: 深度过大的递归调用会消耗过多的调用栈空间,可能引起栈溢出错误。
  • 资源消耗: 相较于循环,递归可能消耗更多的内存资源。

循环(特别是while循环)的优势:

  • 性能高效: 循环通常提供更好的性能表现,尤其是在处理大量数据或深度循环时,因为它们不涉及额外的函数调用开销。
  • 控制灵活: 循环结构提供了更细粒度的控制能力,可以直接管理迭代变量和终止条件。
  • 栈空间友好: 不会导致栈溢出问题,适用于需要处理大规模数据或深度迭代的场景。

循环的劣势:

  • 逻辑复杂: 对于某些自然递归的问题,使用循环实现可能使得代码逻辑较为繁琐,不易于直观理解。

综上所述,选择递归还是循环应基于具体问题的需求、性能考量、代码可读性以及潜在的规模限制。在追求代码简洁和直观性时倾向于递归,在考虑效率和处理大规模数据时则可能更适合使用循环。此外,实际应用中,也可以结合两者优点,如在递归中引入记忆化技术优化性能,或者利用循环结构模拟递归逻辑,达到最佳的解决方案。


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d75eae6a8d804d4b947a329c1a7e823e.png

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【PyTorch实战演练】使用CelebA数据集训练DCGAN(深度卷积生成对抗网络)并生成人脸(附完整代码)

文章目录 0. 前言1. CelebA数据集1.1 核心特性与规模1.2 应用与用途1.3 获取方式1.4 数据预处理 2. DCGAN的模型构建2.1 生成器模型2.2 判别器模型 3. DCGAN的模型训练&#xff08;重点&#xff09;3.1 训练参数3.2 模型参数初始化3.3 训练过程 4. 结果展示4.1 loss值变化过程4…

从零开始!学习绘制3D表情的详细指南

在2020 年的苹果全球开发者大会(WWDC)&#xff0c;苹果发布了新的 macOS 11(又名 Big Sur)。其中在UI视觉方面macOS Big Sur 系统最大的变化就是图标上&#xff0c; Big Sur更新了很多新设计风格的 3D应用图标&#xff0c;3D设计的确可以提升UI整体的视觉氛围&#xff0c;并且现…

Linux——socket编程之tcp通信

前言 前面我们学习socket的udp通信&#xff0c;了解到了socket的概念与udp的实现方法&#xff0c;今天我们来学习一下面向连接的tcp通信。 一、tcp套接字创建 UDP和TCP都是通过套接字&#xff08;socket&#xff09;来实现通信的&#xff0c;因此TCP也得使用socket()接口创建…

企业架构领域的天花板——TOGAF证书

TOGAF证书是一项重要的企业架构认证&#xff0c;为专业人士提供了广泛的知识和技能&#xff0c;帮助他们在企业架构领域取得突破。 一、TOGAF证书的重要 提升专业知识和技能&#xff1a;TOGAF证书提供了广泛的企业架构知识和最佳实践&#xff0c;使专业人士能够更好地理解和应…

ESD静电问题 | 电容谐振频率点及选择

静电枪放出的静电属于共模干扰 针对静电整改加电容的时候选择频率在17.5MHz~350MHz区间的电容 【转自微信公众号&#xff1a;韬略科技EMC】

漫画对话 ai翻译

復讐の教科書ーー81 81-1 いい加減吐け&#xff01;&#xff01;冴木&#xff01;&#xff01; 快说吧&#xff01;&#xff01;冴木&#xff01;&#xff01; お前が一連の事件の犯人なんだろ&#xff01;&#xff1f; 你就是连续事件的犯人吧&#xff01;&#xff1f; だか…

elementUI表格table文字不换行

在对应不需要换行的列加上属性&#xff1a;:show-overflow-tooltip"true" 即可

leetcode 1235

leetcode 1235 代码 class Solution { public:int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {int n startTime.size();vector<vector<int>> jobs(n);for(int i0; i<n; i){jobs[i] …

CMakeLists.txt语法规则:数学运算 math

一. 简介 前面几篇文章学习了 CMakeLists.txt语法中的一些常用变量&#xff0c;常用命令&#xff0c;双引号的作用。条件判断语句&#xff0c;循环语句等等。 本文简单学习一下 CMakeLists.txt语法中数学运算 match。 二. CMakeLists.txt语法规则&#xff1a;数学运算 math 在…

【动态规划】子数组、子串系列I|最大子数组和|环形子数组的最大和|乘积最大子数组|乘积为正数的最长子数组长度

一、最大子数组和 最大子数组和 算法原理&#xff1a; &#x1f4a1;细节&#xff1a; 1.返回值为dp表每个位置的最大值&#xff0c;而不是只看最后一个位置&#xff0c;因为可能最后一个位置都不选 2.可以直接在填dp表的时候就进行返回值的比较 3.如果初始化选择多开一个位…

Apifox 教程:如何实现跨语言调用(Java、PHP、Python、Go 等)

在一些特定场景下&#xff0c;比如需要在 Apifox 中对文件进行读写、加密、转换格式或者进行其它业务的操作时&#xff0c;仅使用 Apifox 内置的 JS 类库可能无法满足业务需求&#xff0c;这时&#xff0c;就可以借助「外部程序」作为解决方案。 外部程序是保存在「外部程序目…

Tower for Mac:Git管理的新境界

Tower for Mac&#xff0c;让您的Git管理进入新境界&#xff01;这款专为Mac用户打造的Git客户端&#xff0c;凭借其出色的性能和丰富的功能&#xff0c;成为众多开发者的首选工具。 Tower不仅支持常规的Git操作&#xff0c;如提交、推送和拉取&#xff0c;还提供了许多高级功能…

AR人脸美妆SDK解决方案,让妆容更加贴合个人风格

美妆行业正迎来前所未有的变革&#xff0c;为满足企业对高效、精准、创新的美妆技术需求&#xff0c;美摄科技倾力打造了一款企业级AR人脸美妆SDK解决方案&#xff0c;为企业打开美妆领域的新世界大门。 革命性的人脸美妆技术 美摄科技的AR人脸美妆SDK解决方案&#xff0c;不…

攻略:ChatGPT3.5~4.0(中文版)国内无限制免费版(附网址)【2024年5月最新更新】

一、什么是ChatGPT&#xff1f; 1、ChatGPT的全名是Chat Generative Pre-trained Transformer&#xff0c;其中"chat"表示聊天。"GPT"则是由三部分组成&#xff1a;生成式&#xff08;generative&#xff09;意味着具有创造力&#xff1b;预训练&#xff0…

计算机毕业设计 | vue+springboot图书借阅 书籍管理系统(附源码)

1. 开发目的 实现图书的智能化、信息化和简单化&#xff1b;实现图书信息的增加、删除、修改、查找、借阅、还书、收藏的显示操作及实时数据库的提交和更改和对普通用户的增、删、改、查&#xff1b;提高图书管理员工作信息报送及反馈的工作效率&#xff0c;减轻管理员的劳动负…

基于Spring Boot框架实现大学生选课管理系统

文章目录 源代码下载地址项目介绍项目功能界面预览 项目备注源代码下载地址 源代码下载地址 点击这里下载源码 项目介绍 项目功能 教务处管理 开课、开班审批&#xff0c;排课处理&#xff0c;班级操作&#xff0c;选课时间段管理** 使用了sql解决了开课开班的时间段的冲突…

Python的Web框架Flask+Vue生成漂亮的词云图

生成效果图 输入待生成词云图的文本&#xff0c;点击生成词云即可&#xff0c;在词云图生成之后&#xff0c;可以点击下载图片保存词云图。 运行步骤 分别用前端和后端编译器&#xff0c;打开backend和frontend文件夹。前端运行 npm install &#xff0c;安装相应的包。后端…

再也不用担心 AI 图片脸崩手崩了

如果你经常用 Stable Diffusion 画人物&#xff0c;相信你一定画出过脸崩的图片。这也是目前文生图 AI 工具普遍存在的问题。连 Midjourney V6 也不例外&#xff01;当它画一个人的时候表现还好&#xff0c;当画面里的人一多&#xff0c;局面就难以控制了。 看&#xff0c;这就…

远动通讯屏的作用

远动通讯屏的作用 远动通讯屏有时有称为调度数据网柜&#xff0c;远动通讯屏具体干啥作用&#xff1f;远动通讯屏是以计算机为基础的生产过程与调度自动化系统&#xff0c;可以对现场的运行设备进行监视和控制、以实现数据采集、设备测量、参数调节以及各类信号报警等各项功能。…

segment anythin 新标注工具 paddleocr训练自己的数据

快递单ocr检测 1.总结2.需求3.方案4.面单定位4.1反转图片扩充数据集4.2新的标注方式4.3json2yolo4.4yolov5推理 5.paddleocr5.1 数据标注5.2 文本检测训练5.3 文本识别训练检测结果 1.总结 按照惯例&#xff0c;先吐槽一下。反正也没人看我比比歪歪。做事全部藏着掖着&#xf…