前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >朋友你听说过尾递归吗

朋友你听说过尾递归吗

作者头像
IMWeb前端团队
发布于 2019-12-04 14:49:02
发布于 2019-12-04 14:49:02
60800
代码可运行
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队
运行总次数:0
代码可运行

1. 尾递归

说起尾递归就不能不提一下尾调用(Tail Call)

尾调用:在函数的最后一步调用另外一个函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function func(){
    // ... other code
    return otherFunc();// 尾调用
}

尾递归:在函数的最后一步调用自身

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function func(){
    // ... other code
    return func();// 尾递归
}

2. 尾调用和非尾调用

说到递归,那就必然要以斐波那契数列为例子

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……

简单的说,斐波那契数列中的每一项都是前两项的和。 即F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>2,n∈N*)

2.1 常见版本

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 实现出来长这样
function fibo(n) {
    if (n <= 0) {
        return 0;
    } else if (n === 1) {
        return 1;
    }
    return fibo(n - 1) + fibo(n - 2)
}

所以手动模拟一下计算fibo(5)的调用栈,大概是这样的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fibo(5)
fibo(4) + fibo(3)
fibo(3) + fibo(2)+  fibo(3)
fibo(2) + fibo(1) + fibo(2)+  fibo(3)
2 + fibo(2) +  fibo(3)
3 + fibo(3)
3 + fibo(2) + fibo(1)
3 + 2
5

在计算的过程中,堆栈需要不停的记录每一层次调用的详细信息(如参数、局部变量、返回地址等等),以确保该层次的操作完成,能返回到上一层次,这些信息再少也会占用一定空间,成千上万个此类空间累积起来,自然就超过线程的栈空间了。于是stackOverflow异常就被抛出了。

2.2 尾递归版本

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function fibo(n, num1 = 1, num2 = 1) {
    if (n <= 0) {
        return 0;
    } else if (n === 1) {
        return num1;
    }
    return fibo4(n - 1, num2, num1 + num2);
}

同样手动模拟一下调用栈

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fibo(5)
fibo(4, 1, 2)
fibo(3, 2, 3)
fibo(2, 3, 5)
fibo(1, 5, 8)

你会发现,尾调用作为函数的最后一步操作,它在某些场景下不需要保存外层函数的调用记录,因为这些信息不会再被用到了(这里是作为参数传入下一次调用了),所以可以以上层调用帧作为尾调用的调用环境。 即

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fibo(5);
// 等同于调用
fibo(1, 5, 8);// 中间的调用帧都不需要保存

使用尾递归,取消过多的堆栈记录,而只记录最外层和当前层的信息,完成计算后,立刻返回到最上层。这也就不会有栈溢出的问题,同时减少了内存以及上下文切换的损耗。

细心的朋友也发现了,尾递归的本质实际上就是将方法需要的上下文通过方法的参数传递进下一次调用之中,以达到去除上层依赖。

但是这同样会引入一些让使用者费解的调用参数,上文使用ES6的默认参数解决调用暴露函数列表的问题,但是依旧可能因为用户多传入的参数导致计算结果不正确。所以你可能需要一些其他的手段解决API的问题,例如封装一层调用方法,函数柯理化等。

3. 性能对比

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 常规递归写法
function fibo(n) {
    if (n <= 0) {
        return 0;
    } else if (n === 1) {
        return 1;
    }
    return fibo(n - 1) + fibo(n - 2)
}

// 常规循环写法
function fibo2(n) {
    if (n <= 0) {
        return 0;
    } else if (n === 1) {
        return 1;
    }
    let x = 0,
        y = 1,
        sum = 0;
    for (let i = 2; i <= n; i++) {
        sum = x + y;
        x = y;
        y = sum;
    }
    return sum;
}

// 公式法
function fibo3(n) {
    if (n <= 0)
        return 0;
    else {
        const sqrtFive = Math.sqrt(5);
        const res = (Math.pow(0.5 + sqrtFive / 2, n) - Math.pow(0.5 - sqrtFive / 2, n)) / sqrtFive;
        return Math.round(res);
    }
}

// 尾递归写法
function fibo4(n, num1 = 1, num2 = 1) {
    if (n <= 0) {
        return 0;
    } else if (n === 1) {
        return num1;
    }
    return fibo4(n - 1, num2, num1 + num2);
}

function test() {
    let tick = Date.now();
    console.log(fibo(50), Date.now() - tick);
    tick = Date.now();
    console.log(fibo2(500), Date.now() - tick);
    tick = Date.now();
    console.log(fibo3(500), Date.now() - tick);
    tick = Date.now();
    console.log(fibo4(500), Date.now() - tick);
    tick = Date.now();
}

运行结果:

通过运行结果我们可以得到一些结论:

  • 慎用直接递归的方式,不仅会带来极差的运行效率,同时会导致浏览器直接无响应。在浏览器环境中,一些代价高昂的计算会导致糟糕的用户体验,因为一个页面的用户界面无响应多数是由于在运行js代码。
  • 尾递归有着与循环同样优秀的计算性能,使用尾递归可以同时拥有着循环的性能以及递归的数学表达能力

4. 一个栈溢出的例子

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 计算1-N的累加值
function f(n) {
    if (n <= 1) {
        return 1;
    }
    return f(n - 1) + n;
}
f(100000);

调用结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 计算1-N的累加值(尾递归)
function f(n, sum = 1) {
    if (n <= 1) {
        return sum;
    }
    return f(n - 1, sum + n);
}
f(100000);

调用结果:

什么鬼,说好的尾递归优化呢?

5. PTC与STC

ES6标准规定了 尾调用不会创建额外的调用帧。 在严格模式下 尾调用不会造成调用栈溢出。 Proper Tail Calls(PTC)已经实现了,但是还未部署,该功能仍然在TC39标准委员会中讨论。

5.1 PTC存在的问题

  • 1 性能

大部分实现者和开发者认为PTC是一种优化策略,使得代码跑的更快。但是可能由于其他的约束,例如兼容以前的功能或顺应标准,部分团队在实现的时候牺牲了性能。

在PTC的实现中,许多调用帧都被抛弃了,导致很难再调用栈中调试他们的代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 举个例子
function foo(n) {
  return bar(n*2);// 尾调用
}

function bar() {
  throw new Error();
}

foo(1);
// 由于尾调用优化
// 在Error.stack或者开发者工具中,foo的调用帧被丢掉了。
  • 3 Error.stack

启用PTC导致Javascript异常有了不一致的error.stack信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*
output without PTC
Error
    at bar
    at foo
    at Global Code

output with PTC (note how it appears that bar is called from Global Code)
Error
    at bar
    at Global Code
*/
  • 4 开发者意图

开发者是否真的从PTC中受益了呢,或许开发者根本就不想自动对尾调用进行优化呢?接下来讲到的STC即可避免其他事情重构的危险,有着更明确的语义。

5.2 STC

语义上的尾调用(Syntactic Tail Call)是针对上述PTC的问题而提出的建议。 STC采用类似于 return continue 的语法来明确标识出要进行尾调用优化,而在非尾调用的场景下使用该语法会抛出语法错误异常。 该语法有三种实现形式:

  • 语法级
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function factorial(n, acc = 1) {
  if (n === 1) {
    return acc;
  }

  return continue factorial(n - 1, acc * n)
}
  • 函数级
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#function() { /* all calls in tail position are tail calls */ }
  • 表达式/调用点
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function () { 
    !return expr 
}

更多内容可参考:proposal-ptc-syntax

6. 那么问题来了

挖掘机技术 ... 呸。

我们以斐波那契数列为例子讲解了尾递归的运用方式,并对比了普通递归与尾递归的性能。

通过实验我们能够确定尾递归调用确实帮助我们调优了程序性能(第三节内容),但是通过第四节的实验我们发现依旧不能避免调用栈溢出的问题,而ES6的标准里面规定了尾调用的优化中是不会创建新的调用帧的。

那么尾递归的方式依旧出现了调用栈溢出的原因究竟是什么呢?

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016-12-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
4 条评论
热度
最新
请问作者在哪里可以找到这篇文章的原文呢?为什么我在谷歌学术上没有发现这篇文章呢?请问文章的题目是什么呢?求求了
请问作者在哪里可以找到这篇文章的原文呢?为什么我在谷歌学术上没有发现这篇文章呢?请问文章的题目是什么呢?求求了
回复回复点赞举报
大佬 方便分享一下网络结构的代码吗
大佬 方便分享一下网络结构的代码吗
回复回复点赞举报
感谢大佬分享
感谢大佬分享
回复回复点赞举报
学习了
学习了
回复回复点赞举报
推荐阅读
文本数据的机器学习自动分类方法(上)
【编者按】:随着互联网技术的迅速发展与普及,如何对浩如烟海的数据进行分类、组织和管理,已经成为一个具有重要用途的研究课题。而在这些数据中,文本数据又是数量最大的一类。以统计理论为基础,利用机器学习算法对已知的训练数据做统计分析从而获得规律,再运用规律对未知数据做预测分析,已成为文本分类领域的主流。InfoQ联合“达观数据“共同策划了《文本数据的机器学习自动分类方法》系列文章,为您详细阐述机器学习文本分类的基本方法与处理流程。 本文为第一部分,着重介绍文本预处理以及特征抽取的方法。第二部分将会着重介绍特征向量
小莹莹
2018/04/23
2.1K0
文本数据的机器学习自动分类方法(上)
课堂总结 | 达观数据文本挖掘负责人分享文本分类方法和应用案例
新媒体管家 自然语言处理(NLP)一直是人工智能领域的重要话题,而人类语言的复杂性也给NLP布下了重重困难等待解决。随着深度学习(Deep Learning)的热潮来临,有许多新方法来到了NLP领域,给相关任务带来了更多优秀成果,也给大家带来了更多应用和想象的空间。 近期,达观数据文本挖掘组负责人张健应邀在雷锋网AI研习社分享了一些NLP方面的知识和案例。 1 达观文本挖掘系统整体方案 达观文本挖掘系统整体方案包含了NLP处理的各个环节,从处理的文本粒度上来分,可以分为篇章级应用、短串级应用和词汇级应用
达观数据
2018/03/30
1.5K0
课堂总结 |  达观数据文本挖掘负责人分享文本分类方法和应用案例
用深度学习(CNN RNN Attention)解决大规模文本分类问题 - 综述和实践
近来在同时做一个应用深度学习解决淘宝商品的类目预测问题的项目,恰好硕士毕业时论文题目便是文本分类问题,趁此机会总结下文本分类领域特别是应用深度学习解决文本分类的相关的思路、做法和部分实践的经验。
CreateAMind
2018/07/24
2K0
用深度学习(CNN RNN Attention)解决大规模文本分类问题 - 综述和实践
达观数据分享文本大数据的机器学习自动分类方法
随着互联网技术的迅速发展与普及,如何对浩如烟海的数据进行分类、组织和管理,已经成为一个具有重要用途的研究课题。而在这些数据中,文本数据又是数量最大的一类。文本分类是指在给定分类体系下,根据文本内容自动确定文本类别的过程(达观数据科技联合创始人张健)。文本分类有着广泛的应用场景,例如: ●新闻网站包含大量报道文章,基于文章内容,需要将这些文章按题材进行自动分类(例如自动划分成政治、经济、军事、体育、娱乐等) ●在电子商务网站,用户进行了交易行为后对商品进行评价分类,商家需要对用户的评价划分为正面评价和负面评价
达观数据
2018/03/30
1.3K0
NLP概述和文本自动分类算法详解 | 公开课笔记
文本挖掘任务大致分为四个类型:类别到序列、序列到类别、同步的(每个输入位置都要产生输出)序列到序列、异步的序列到序列。
用户1737318
2019/11/19
1.8K0
NLP概述和文本自动分类算法详解 | 公开课笔记
达观数据NLP技术的应用实践和案例分析
达观文本挖掘系统整体方案 达观文本挖掘系统整体方案包含了NLP处理的各个环节,从处理的文本粒度上来分,可以分为篇章级应用、短串级应用和词汇级应用。 篇章级应用有六个方面,已经有成熟的产品支持企业在不同方面的文本挖掘需求: 垃圾评论:精准识别广告、不文明用语及低质量文本。 黄反识别:准确定位文本中所含涉黄、涉政及反动内容。 标签提取:提取文本中的核心词语生成标签。 文章分类:依据预设分类体系对文本进行自动归类。 情感分析:准确分析用户透过文本表达出的情感倾向。 文章主题模型:抽取出文章的隐
机器学习AI算法工程
2018/03/15
1.6K0
达观数据NLP技术的应用实践和案例分析
基于keras的文本分类实践基于keras的文本分类实践
文本分类是自然语言处理中一个很经典也很重要的问题,它的应用很广泛,在很多领域发挥着重要作用,例如垃圾邮件过滤、舆情分析以及新闻分类等。和其他的分类问题一样,文本分类的核心问题首先是从文本中提取出分类数据的特征,然后选择合适的分类算法和模型对特征进行建模,从而实现分类。当然文本分类问题又具有自身的特点,例如文本分类需要对文本进行分词等预处理,然后选择合适的方法对文本进行特征表示,然后构建分类器对其进行分类。本文希望通过实践的方式对文本分类中的一些重要分类模型进行总结和实践,尽可能将这些模型联系起来,利用通俗易懂的方式让大家对这些模型有所了解,方便大家在今后的工作学习中选择文本分类模型。
绿盟科技研究通讯
2019/12/11
1.3K0
基于keras的文本分类实践基于keras的文本分类实践
大话文本分类
概述 文本分类是自然语言处理的重要应用,也可以说是最基础的应用。常见的文本分类应用有:新闻文本分类、信息检索、情感分析、意图判断等。本文主要针对文本分类的方法进行简单总结。 01 — 传统机器学习方法 分类问题一般的步骤可以分为特征提取、模型构建、算法寻优、交叉验证等。对于文本而言,如何进行特征提取是一个很重要也很有挑战性的问题。文本的特征是什么,如何量化为数学表达呢。 最开始的文本分类是基于规则的,特征就是关键词,例如足球在体育类出现的次数多,就将含有足球这一关键词的文本氛围体育。后来为了便于计算,通过
CodeInHand
2018/03/26
1.6K0
大话文本分类
LSTM文本分类实战
作者:王千发 编辑:龚 赛 什么是文本分类 1 文本分类在文本处理中是很重要的一个模块,它的应用也非常广泛,比如:垃圾过滤,新闻分类,等等。传统的文本分类方法的流程基本是: 预处理:首先进行分词,然后是除去停用词; 将文本表示成向量,常用的就是文本表示向量空间模型; 进行特征选择,这里的特征就是词语,去掉一些对于分类帮助不大的特征。常用的特征选择的方法是词频过滤,互信息,信息增益,卡方检验等; 接下来就是构造分类器,在文本分类中常用的分类器一般是SVM,朴素贝叶斯等; 训练分类器,后面
机器学习算法工程师
2018/03/06
4.9K0
LSTM文本分类实战
基于机器学习的文本分类算法的研究[通俗易懂]
文本分类的方法属于有监督的学习方法,分类过程包括文本预处理、特征抽取、降维、分类和模型评价。本文首先研究了文本分类的背景,中文分词算法。然后是对各种各样的特征抽取进行研究,包括词项频率-逆文档频率和word2vec,降维方法有主成分分析法和潜在索引分析,最后是对分类算法进行研究,包括朴素贝叶斯的多变量贝努利模型和多项式模型,支持向量机和深度学习方法。深度学习方法包括多层感知机,卷积神经网络和循环神经网络。
全栈程序员站长
2022/06/27
9160
基于机器学习的文本分类算法的研究[通俗易懂]
Text-CNN、Word2Vec、RNN、NLP、Keras、fast.ai-20180504
本文集仅为收录自己感兴趣、感觉不错的文章与资源,方便日后查找和阅读,所以排版可能会让人觉得乱。内容会不断更新与调整。文中涉及公众号的文章链接可以会失效,知道如何生成永久链接的小伙伴还望告知。
古柳_DesertsX
2018/08/21
9200
Text-CNN、Word2Vec、RNN、NLP、Keras、fast.ai-20180504
数据分析:文本分类
本章节中所涉及的知识点偏向于机器学习的范畴,那么机器学习和数据分析有什么区别呢。简单来讲,数据分析是少量数据采样分析而机器学习是海量数据全部分析。比较好的理解一点是,数据分析会总结过去已经发生的事情,而机器学习是为了预测未来发生的事情。这两者也是有相辅相成的关系。我们可以通过机器学习预测的结果,进行数据分析,得到一个相对准确的结论,辅助人们进行决策判断等等。
马拉松程序员
2023/09/02
4010
数据分析:文本分类
【陆勤学习】文本特征提取方法研究
一、课题背景概述 文本挖掘是一门交叉性学科,涉及数据挖掘、机器学习、模式识别、人工智能、统计学、计算机语言学、计算机网络技术、信息学等多个领域。文本挖掘就是从大量的文档中发现隐含知识和模式的一种方法和工具,它从数据挖掘发展而来,但与传统的数据挖掘又有许多不同。文本挖掘的对象是海量、异构、分布的文档(web);文档内容是人类所使用的自然语言,缺乏计算机可理解的语义。传统数据挖掘所处理的数据是结构化的,而文档(web)都是半结构或无结构的。所以,文本挖掘面临的首要问题是如何在计算机中合理地表示文本,使之既要包含
陆勤_数据人网
2018/02/26
1.1K0
【2023】数据挖掘课程设计:基于TF-IDF的文本分类
PyCharm 2022.3.1 (Professional Edition)
Qomolangma
2024/07/29
1490
【2023】数据挖掘课程设计:基于TF-IDF的文本分类
文本挖掘的介绍
文本挖掘是指从大量文本的集合C中发现隐含的模式p。如果将C看作输入,将p看作输出,那么文本挖掘的过程就是从输入到输出的一个映射ξ:C→ p。
全栈程序员站长
2022/09/07
1.3K0
文本挖掘的介绍
Python人工智能 | 二十一.CNN和Word2Vec中文文本分类详解及与机器学习分类对比
从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前一篇文章分享了Keras实现RNN和LSTM的文本分类算法,并与传统的机器学习分类算法进行对比实验。这篇文章我们将继续巩固文本分类知识,主要讲解CNN实现中文文本分类的过程,并与贝叶斯、决策树、逻辑回归、随机森林、KNN、SVM等分类算法进行对比。注意,本文以代码为主,文本分类叙述及算法原理推荐阅读前面的文章。基础性文章,希望对您喜欢~
Eastmount
2023/02/28
3.3K0
Python人工智能 | 二十一.CNN和Word2Vec中文文本分类详解及与机器学习分类对比
网络挖掘技术——微博文本特征提取
文本特征向量 经典的向量空间模型(VSM: Vector Space Model)由Salton等人于60年代提出,并成功地应用于著名的SMART文本检索系统。VSM概念简单,把对文本内容的处理简化为向量空间中的向量运算,并且它以空间上的相似度表达语义的相似度,直观易懂。当文档被表示为文档空间的向量,就可以通过计算向量之间的相似性来度量文档间的相似性。文本处理中最常用的相似性度量方式是余弦距离。文本挖掘系统采用向量空间模型,用特征词条(T1 ,T2 ,…Tn) 及其权值Wi 代表目标信息,在进行信息匹配时,
机器学习AI算法工程
2018/03/13
1.3K0
【文本分类】基于DNN/CNN的情感分类
导语 PaddlePaddle提供了丰富的运算单元,帮助大家以模块化的方式构建起千变万化的深度学习模型来解决不同的应用问题。这里,我们针对常见的机器学习任务,提供了不同的神经网络模型供大家学习和使用。本周推文目录如下: 周一:【点击率预估】 Wide&deep 点击率预估模型 周二:【文本分类】 基于DNN/CNN的情感分类 周三:【文本分类】 基于双层序列的文本分类模型 周四:【排序学习】 基于Pairwise和Listwise的排序学习 周五:【结构化语义模型】 深度结构化语义模型 文本分类是自然语言
用户1386409
2018/03/15
1.8K0
【文本分类】基于DNN/CNN的情感分类
(二)中文文本分类--机器学习算法原理与编程实践 - 简书
本章知识点:中文分词,向量空间模型,TF-IDF方法,文本分类算法和评价指标 使用的算法:朴素的贝叶斯算法,KNN最近邻算法 python库:jieba分词,Scikit-Learning 本章目标:实现小型的文本分类系统 本章主要讲解文本分类的整体流程和相关算法
会呼吸的Coder
2020/02/17
1.5K0
第二章--第三篇---文本分类
文本分类是一种基于自然语言处理技术,对给定的文本进行分类的方法。具体而言,文本分类将一篇文本分配到一个或多个预定义的类别中,这些类别通常是事先定义好的,例如新闻、评论、垃圾邮件、商品分类等。 文本分类在实际应用中有着广泛的应用,例如在舆情监控、垃圾邮件过滤、新闻分类、商品分类、情感分析等领域。通过对海量文本数据进行分类,可以帮助用户快速准确地获得所需信息,从而提高效率。此外,文本分类还可以帮助企业识别消费者的意见和情感倾向,为其提供更好的产品和服务,增强市场竞争力。
喵叔
2023/05/11
4780
推荐阅读
相关推荐
文本数据的机器学习自动分类方法(上)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档