Javascript函数之深入浅出递归思想
一、递归函数的理解
1、生活中的递归

“递归”在生活中的一个典例就是“问路”。如图小哥哥进入电影院后找不到自己的座位,问身边的小姐姐“这是第几排”,小姐姐也不清楚便依次向前询问,问至第一排的观众后依次向后反馈结果,“我是第一排”,“我是第二排”,···,最终确定自己座位所在排数。
在这个过程中充分反应了“传递”(询问)和“回归”(反馈)的思想,故将这种现象称为“递归”。
2、编程中的递归
计算机有两个特点:“很笨”又“很快”。所以将“复杂问题”转化为“多步骤的简单问题”后,计算机才能高效执行。
而递归是编程算法的一种,通过调用自身,将一些复杂的问题简单化,便于得出解决方案。
下面通过简单的案例了解编程中的递归
案例:计算1+2+3+4+5+6的和。
function fn(n){
if(n === 1){
return 1;
}
return n + fn(n - 1);
}
fn(6);
计算过程:
f(6) = 6 + f(5)
f(6) = 6 + 5 + f(4)
f(6) = 6 + 5 + 4 + f(3)
f(6) = 6 + 5 + 4 + 3 + f(2)
f(6) = 6 + 5 + 4 + 3 + 2 + f(1)
f(6) = 6 + 5 + 4 + 3 + 2 + 1
由上可知递归函数的 本质:
- 调用自身
递归函数的实现有 两个要素:
- 终止条件
- 逐步靠近终止条件
案例中的 终止条件 是:当 n === 1 时,fn(1) === 1。 若没有终止条件,函数会继续计算f(0) 、f(-1) 、f(-2) ··· 从而进入死循环,无法得出结果。
通过计算过程可以看出,函数依次计算f(6)、f(5)、f(4)、f(3)、 f(2)、f(1),很好的满足了第二个要素 逐步靠近终止条件。
二、递归函数的使用
通过以上讲解,想必已经了解递归函数的原理,
那么递归函数是如何写出来的呢?
如何利用递归函数解决实际问题呢?
实例探索递归函数的书写“套路”
例题:计算n的阶乘。
步骤1:找到终止条件,写给 if
数学知识 :n! = n * (n - 1) * (n - 2) * (n -3) * ··· * 3 * 2 * 1
显然 终止条件 是 n === 1; 时, return 1;
故可以完成函数的 前半部分:
function fn(n){
if(n === 1){
return 1;
}
// 未完待续
}
步骤2:找到函数的等价关系式,写给 return
数学知识 :n! = n * (n - 1)!
通过数学知识 n! = n * (n - 1)! 和递归函数的本质(调用自身),
可以得出函数的等价关系式为 f(n) = n * f(n - 1);
从而可以完成函数的 后半部分:
function fn(n){
if(n === 1){
return 1;
}
return n * fn(n - 1);
}
至此简单的递归函数便写出来了,递归函数最大的特点便是代码简洁,简洁到让人心虚。
总结,递归函数的书写“套路”
- 找到终止条件,写给 if
- 找到函数的等价关系式,写给 return
三、递归函数的问题
想必你会说,上面的两个例题用 循环 就能轻松写出来,为何还需要使用递归呢?
其实能用 递归 解决的问题,用 循环 也能解决!而且 递归 比 循环 的运算速度要慢,因为 递归 需要逐层调用函数,占据系统内存,当 递归 层级较深时,对性能消耗较大,往往不推荐使用。
问:那 递归 存在的意义是什么?
递归 是为了将复杂问题简单化,提供解题思路,进而得到 “循环算法”
对于简单问题,一眼便能看出“循环算法”,但对于抽象问题,通常可以先采取 递归 思想,如:
例题:某人需要走上10级台阶,有两种走法,走法A:一步1个台阶;走法B:一步2个台阶。两种走法可以任意交替使用,问走上10级台阶共有多少种方法?

这个问题很难直接看出 循环 的解题思路,我们不妨从 递归 的角度尝试解决:
当走上第10级台阶只差最后一步时,存在有两种可能:
第1种:从 第8级 ---> 第10级(一步2个台阶)
第2种:从 第9级 ---> 第10级(一步1个台阶)
假设:从 第0级 ---> 第8级,有 x 种走法;
1,1,1,1,1,1,2,2
1,1,1,1,1,2,1,2
1,2,1,1,1,2,2
1,2,1,2,2,2
·······
// 穷举不尽,共 x 种,每种走法的最后一步都是 2(个台阶)
假设:从 第0级 ---> 第9级,有 y 种走法;
1,1,1,1,1,1,1,2,1
1,1,2,1,1,2,1,1
1,2,1,2,2,1,1
1,2,2,2,2,1
·······
// 穷举不尽,共 y 种,每种走法的最后一步都是 1(个台阶)
那么,从 第0级 ---> 第10级,共有 x + y 种走法。
故,10级台阶走法 = 9级台阶走法 + 8级台阶走法,即 f(10) = f(9) + f(8);
所以我们需要的 函数关系式 是 f(n) = f(n - 1) + f(n - 2);
接下来找 终止条件:
1级台阶时,走法只有1种(1步1台阶),是 n === 1; 时, return 1;
2级台阶时,走法只有2种(2次1步1台阶 或 1步2台阶),是 n === 2; 时, return 2;
由此可以写出 递归函数
function fn(n){
if(n === 1 || n === 2){
return n;
}
return fun(n - 1) + fun(n - 2);
}
下图展示了函数的 执行过程

可见,在函数执行过程中重复调用了多次相同的函数(相同背景色),从而极大消耗了系统的性能。经过测试这个 递归函数 最多可计算至 f(45);左右的结果(测试需谨慎),这便是 递归函数 存在的主要问题。
那么如何优化这个问题呢?
即,将 递归算法 改为 循环算法。
通过前面的推导我们知道 f(n) = f(n - 1) + f(n - 2);
1级台阶 ==> 走法:1
2级台阶 ==> 走法:2
3级台阶 ==> 走法:1 + 2 = 3
4级台阶 ==> 走法:2 + 3 = 5
5级台阶 ==> 走法:3 + 5 = 8
6级台阶 ==> 走法:5 + 8 = 13
7级台阶 ==> 走法:8 + 13 = 21
······
即,只要知道前两项(1级台阶和2级台阶)的结果,就可以自底向上依次推算出后面所有项的结果
于是便可以写出 循环函数
function fn(n){
if(n === 1 || n === 2){
return n;
}
var left = 1; // 左边的数据
var right = 2; // 右边的数据
var sum = 0;
for(var i = 3 ; i <= n ; i++){ // 循环从第3项开始
sum = left + right; // 计算前一次左右数据的和
left = right; // 把前一次的right赋值给下一次的left
right = sum; // 把前一次的和赋值给下一次的right
}
return sum;
}
以上便是通过 递归思想 将抽象问题逐步简单化,从而得出 循环算法 的过程。
循环算法 解决了 递归 消耗系统性能的问题,可以计算任意数值。
(当数值太大超出JavaScript数值范围时,返回 Infinity)
总结:
1、递归结构简单,易理解,常用于将抽象问题简单化。
2、递归要有终止条件,否则会变成死递归;
3、递归算法运行效率低、性能消耗大,递归深度较大时慎用(等不到结果);
4、能用递归解决的问题大多都能用循环解决。
Javascript函数之深入浅出递归思想的更多相关文章
- 前端笔试题目总结——应用JavaScript函数递归打印数组到HTML页面上
数组如下: var item=[{ name:'Tom', age:70, child:[{ name:'Jerry', age:50, child:[{ name:'William', age:20 ...
- JavaScript函数之实际参数对象(arguments) / callee属性 / caller属性 / 递归调用 / 获取函数名称的方法
函数的作用域:调用对象 JavaScript中函数的主体是在局部作用域中执行的,该作用域不同于全局作用域.这个新的作用域是通过将调用对象添加到作用域链的头部而创建的(没怎么理解这句话,有理解的亲可以留 ...
- JavaScript函数使用技巧
JavaScript中的函数是整个语言中最有趣的一部分,它们强大而且灵活.接下来,我们来讨论JavaScript中函数的一些常用技巧: 一.函数绑定 函数绑定是指创建一个函数,可以在特定的this环境 ...
- javascript 函数初探 (一)--- 神马是函数
神马是函数? 所谓函数,本质上是一种代码的分组形式.我们可以通过这种形式赋予某组代码一个名字,以便与之后的调用.下面,我们来示范以下函数的声明: function sum(a, b){ var c = ...
- Javascript函数重载,存在呢—还是存在呢?
1.What's is 函数重载? );//Here is int 10 print("ten");//Here is string ten } 可以发现在C++中会根据参数的类型 ...
- JavaScript语言精粹读书笔记 - JavaScript函数
JavaScript是披着C族语言外衣的LISP,除了词法上与C族语言相似以外,其他几乎没有相似之处. JavaScript 函数: 函数包含一组语句,他们是JavaScript的基础模块单元,用于代 ...
- 浅谈javascript函数节流
浅谈javascript函数节流 什么是函数节流? 函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等 ...
- 简述JavaScript函数节流
为什么要用函数节流 浏览器中某些计算和处理要比其他的昂贵很多.例如,DOM 操作比起非 DOM 交互需要更多的内存和 CPU 时间.连续尝试进行过多的 DOM 相关操作可能会导致浏览器挂起,有时候甚至 ...
- javascript arguments与javascript函数重载
1.所 有的函数都有属于自己的一个arguments对象,它包括了函所要调用的参数.他不是一个数组,如果用typeof arguments,返回的是’object’.虽然我们可以用调用数据的方法来调用 ...
随机推荐
- 公司更需要会哪种语言的工程师?IEEE Spectrum榜单发布
IEEE Spectrum 杂志发布了一年一度的编程语言排行榜,这也是他们发布的第四届编程语言 Top 榜. 据介绍,IEEE Spectrum 的排序是来自 10 个重要线上数据源的综合,例如 St ...
- JavaScript的封装和继承
提到JavaScript"面向对象编程",主要就是封装和继承,这里主要依据阮一峰及其他博客的系列文章做个总结. 继承机制的设计思想 所有实例对象需要共享的属性和方法,都放在这个对象 ...
- CSS——NO.6(盒模型)
*/ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:text.cpp * 作者:常轩 * 微信公众号:Worldhe ...
- 带你学习ES5中新增的方法
1. ES5中新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括以下几个方面 数组方法 字符串方法 对象方法 2. 数组方法 迭代遍历方法:forEach().map().filter( ...
- LeetCode--二叉树2--运用递归解决树的问题
LeetCode--二叉树2--运用递归解决树的问题 在前面的章节中,我们已经介绍了如何利用递归求解树的遍历. 递归是解决树的相关问题最有效和最常用的方法之一. 我们知道,树可以以递归的方式定义为一个 ...
- session和el表达式
2015/1/21 ## 回顾昨天案例 ## # 模拟购物车: >> 基本步骤: |-- 显示所有的书籍: |-- 制作书记列表/模仿数据库: |-- 参见昨天示例: |-- 制作查看详情 ...
- Nginx之反向代理配置(一)
前文我们聊了下Nginx作为web服务器配置https.日志模块的常用配置.rewrite模块重写用户请求的url,回顾请参考https://www.cnblogs.com/qiuhom-1874/p ...
- 使用 custom element 创建自定义元素
很早我们就可以在 HTML 文档中写 <custome-element></custom-element> 这样的自定义名称标签.但是浏览器对于不认识的标签一律当成一个普通的行 ...
- Javascript元编程之Annotation
语言的自由度 自由度这个概念在不同领域有不同的定义,我们借鉴数学中构成一个空间的维数来表达其自由度的做法,在此指的是:解决同一个问题彼此不相关的设计方法学数量. 例如,解决一个比如商品打折的问题,如何 ...
- Ubuntu几秒钟没有任何操作自动黑屏
在鼠标或键盘30秒内没有做任何操作以后,显示器自动黑屏. 重新点击鼠标或键盘,屏幕唤醒. 设置中心各种设置方式都已经尝试过.无效. $xset -q // 执行该命令 Keyboard Control ...