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

由上可知递归函数的 本质:

  • 调用自身

递归函数的实现有 两个要素:

  1. 终止条件
  2. 逐步靠近终止条件

案例中的 终止条件 是:当 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);
}

至此简单的递归函数便写出来了,递归函数最大的特点便是代码简洁,简洁到让人心虚

总结,递归函数的书写“套路”

  1. 找到终止条件,写给 if
  2. 找到函数的等价关系式,写给 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、能用递归解决的问题大多都能用循环解决。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352