了解JavaScript递归


介绍

使用递归可以更自然地解决一些问题。 例如,像Fibonacci序列这样的序列具有递归定义。 序列中的每个数字都是序列中前两个数字的和。 要求您建立或遍历树状数据结构的问题也可以通过递归解决。 训练自己进行递归思考将为您提供解决此类问题的强大技能。

在本教程中,我将逐步介绍几个递归函数,以了解它们的工作原理,并向您展示可用于系统定义递归函数的技术。

内容:

  • 什么是递归?
  • 带数字递归
  • 列表递归
  • 建筑清单
  • 评论

什么是递归?

递归定义的函数是根据其自身的简单版本定义的函数。 这是一个简化的示例:

function doA(n) {
    ...
    doA(n-1);
}

为了理解递归在概念上是如何工作的,我们将看一个与代码无关的示例。 想象一下,您有责任在工作中接听电话。 由于这家公司很忙,您的电话有多条电话线,因此您可以同时处理多个电话。 每条电话线都是接收器上的一个按钮,当有来电时,该按钮将闪烁。 今天,当您上班并打开手机电源时,有四条线路同时闪烁。 这样您就可以接听所有电话。

您选择第一行,并告诉他们“请稍等”。 然后,您拿起第二行并将其保留。 接下来,您选择第3行并将其保留。 最后,您在第四行接听电话并与呼叫者通话。 当第四个呼叫者结束时,您会挂断并接听第三个呼叫。 当您完成第三个电话时,您会挂断并接听第二个电话。 完成第二个呼叫后,您会挂断并接听第一个电话。 结束通话后,您终于可以放下电话了。

此示例中的每个电话呼叫都类似于函数中的递归呼叫。 接到电话后,它将被放入调用堆栈中(用代码说)。 如果您无法立即完成通话,则将通话置于保持状态。 如果您有无法立即求值的函数调用,它将保留在调用堆栈中。 当您能够接听电话时,便接听电话。 当您的代码能够评估函数调用时,它就会从堆栈中弹出。 在看下面的代码示例时,请记住这个类比。

带数字递归

所有递归函数都需要基本情况​​,因此它们将终止。 但是,仅将基本情况添加到我们的函数中并不能阻止它无限运行。 该功能必须采取步骤使我们更接近基本情况。 最后是递归步骤。 在递归步骤中,问题被简化为问题的较小版本。

假设您有一个将1到n的数字相加的函数。 例如,如果n = 4,则总和为1 + 2 + 3 + 4。

首先,我们确定基本情况。 找到基本案例也可以认为是找到无需递归即可解决问题的案例。 在这种情况下,n等于零。 零没有任何部分,因此递归可以在达到0时停止。

在每一步中,您将从当前数字中减去一个。 什么是递归情况? 递归情况是用减少的数字调用的函数和。

function sum(num){
    if (num === 0) {
        return 0;
    } else {
        return num + sum(--num)
    }
}

sum(4);     //10

这是每个步骤正在发生的事情:

  • 转到sum(4)。
  • 4等于0吗? 否。将sum(4)保留,然后转到sum(3)。
  • 3等于0吗? 否。搁置sum(3)并转到sum(2)。
  • 2等于0吗? 否。搁置sum(2)并转到sum(1)。
  • 1等于0吗? 否。搁置sum(1)并转到sum(0)。
  • 0等于0吗? 是。 评估总和(0)。
  • 拿起sum(1)。
  • 拿起sum(2)。
  • 拿起sum(3)。
  • 拿起sum(4)。

这是查看函数如何处理每个调用的另一种方式:

sum(4)
4 + sum(3)
4 + ( 3 + sum(2) )
4 + ( 3 + ( 2 + sum(1) ))
4 + ( 3 + ( 2 + ( 1 + sum(0) )))
4 + ( 3 + ( 2 + ( 1 + 0 ) ))
4 + ( 3 + ( 2 + 1 ) )
4 + ( 3 +  3 ) 
4 + 6 
10

在递归情况下,该参数应发生变化,并使您更接近基本情况。 此参数应在基本情况下进行测试。 在前面的示例中,由于在递归情况下要减去1,因此我们在基本情况下测试参数是否等于零。

任务

  1. 使用循环而不是递归来实现sum函数。
  2. 创建一个将两个数字递归相乘的函数。 例如, multiply(2,4)将返回8。为multiply(2,4)编写每一步所发生的事情。

列表递归

在列表上重复执行与在编号上重复执行类似,不同之处在于,我们不减少每一步的数目,而是减少每一步的数目,直到得到一个空列表,而不是减少每一步的数目。

考虑以列表为输入并返回列表中所有元素之和的sum函数。 这是函数sum的实现:

function sum(l){
    if (empty(l)) {
        return 0;
    } else {
        return car(l) + sum(cdr(l));
    }
}

如果列表没有元素,则empty函数返回true。 car函数返回列表中的第一个元素。 例如, car([1,2,3,4])返回cdr函数返回不包含第一个元素的列表。 例如, cdr([1,2,3,4])返回[2,3,4]。 当我们执行sum([1,2,3,4])会发生什么?

sum([1,2,3,4])
1 + sum([2,3,4])
1 + ( 2 + sum([3,4]) )
1 + ( 2 + ( 3 + sum([4]) ))
1 + ( 2 + ( 3 + ( 4 + sum([]) )))
1 + ( 2 + ( 3 + ( 4 + 0 ) ))
1 + ( 2 + ( 3 + 4 ) )
1 + ( 2 + 7 ) 
1 + 9
10

当重复出现在列表上时,检查它是否为空。 否则,对列表的精简版本执行递归步骤。

任务

  1. 重写此求和函数,以便它使用循环对列表中的每个项目求和,而不是递归。
  2. 定义一个名为length的函数,该函数将一个列表作为输入并返回该列表中的元素数。 您不应该使用JavaScript的内置长度函数。 例如, length(['a', 'b', 'c', 'd'])应该返回4。写出每个步骤发生的情况。

建筑清单

在最后一个示例中,我们返回一个数字。 但是假设我们想返回一个列表。 这意味着,除了向递归步骤添加数字之外,我们还需要添加列表。 考虑函数remove ,该函数将一个项目和一个列表作为输入,并返回移除了该项目的列表。 仅第一个找到的项目将被删除。

function remove(item, l){
    if (empty(l)) {
        return [];
    } else if (eq(car(l), item)) {
        return cdr(l);
    } else {
        return cons(car(l), remove(item, cdr(l)));
    }
}

remove('c', ['a', 'b', 'c', 'd'])       //[‘a’, ‘b’, ‘d’]

在此,如果两个输入相同,则eq函数返回true。 cons函数将一个元素和一个列表作为输入,并返回一个新列表,并将该元素添加到其开头。

我们将检查列表中的第一项是否等于我们要删除的项。 如果是,请从列表中删除第一个元素,然后返回新列表。 如果第一项与我们要删除的项不相等,我们将列表中的第一个元素添加到递归步骤中。 递归步骤将包含删除了第一个元素的列表。

我们将继续删除元素,直到达到基本情况为止,这是一个空列表。 空列表表示我们遍历了列表中的所有项目。 remove('c', ['a', 'b', 'c', 'd'])什么作用?

remove('c', ['a', 'b', 'c', 'd'])
cons( ‘a’,  remove(‘c’, [‘b’, ‘c’, ‘d’]) )
cons( ‘a’ , cons( ‘b’, remove(‘c’, [‘c’, ‘d’]) ))
cons( ‘a’, cons( ‘b’, [‘d’] )
cons( ‘a’, [‘b’, ‘d’])
[‘a’, ‘b’, ‘d’]

在需要构建列表的情况下,我们采用第一个元素并将其添加到列表的递归部分。

任务

  1. 重写remove函数,以便它使用循环而不是递归从列表中删除元素。
  2. 更改删除功能,以便从列表中删除所有出现的项目。 例如, remove('c', ['a', 'b', 'c', 'd', 'c']返回['a','b','d']。逐步写出发生的情况步。

评论

递归函数包括三个部分。 首先是基本情况,这是终止条件。 第二步是使我们更接近基本情况的步骤。 第三步是递归步骤,其中函数使用减少的输入来调用自身。

递归就像迭代。 您可以递归定义的任何函数也可以使用循环来定义。 使用递归时要考虑的其他事项是在嵌套列表上重现并优化递归调用。

《小计划者 》一书是继续学习递归的重要资源。 它教您如何使用问答格式进行递归思考。

翻译自: https://code.tutsplus.com/tutorials/understanding-recursion-with-javascript--cms-30346

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值