聊聊递归

170 阅读2分钟

是我参与11月更文挑战的第17天, 活动详情查看:2021最后一次更文挑战

定义

递归是一种解决问题的方法,它解决问题的各个小部分,直到解决最初的大问题。通常涉及函数调用自身。

// 第一种
var recursiveFunction = function(someParam){
    recursiveFunction(someParam);
}

// 第二种
var recursiveFunction1 = function(someParam){
    recursiveFunction2(someParam);
}
var recursiveFunction2 = function(someParam){
    recursiveFunction1(someParam);
}

上面两种都是递归函数。第一种是直接调用自身,第二种是样间接调用自身的方法或者函数。

递归的两大要素:递归方程终止条件

js调用栈大小的限制

如果忘记加上用以停止函数递归调用的边界条件即终止条件,会发生什么呢?递归并不会无限地执行下去;浏览器会抛出错误,也就是所谓的栈溢出错误(stack overflow error)。

var i = 0;
function recursiveFn () {
    i++;
    recursiveFn(); // {1}
}

try{
    recursiveFn();
} catch (error) {
    console.log('i = ' + i + 'error:' + error);
}

ECMAScript 6有尾调用优化(tail call optimization)。如果函数内最后一个操作是调用函数(第一行),会通过“跳转指令”(jump) 而不是“子程序调用”(subroutine call)来控制。也就是说,在ECMAScript 6中,这里的代码可以一直执行下去。所以,具有停止递归的边界条件非常重要。

斐波那契数列

这是聊递归必说的例子。

斐波那契数据的定义:

  • 1和2的斐波那契数是 1;
  • n(n>2)的斐波那契数是(n-1)的斐波那契数加上(n-2)的斐波那契数。 代码实现:
function fibonacci(num){
    if(num === 1 || num === 2){
        return 1;
    }
    return fibonacci(num-1) + fibonacci(num-2);
}

当n大于2时,Fibonacci(n)等于Fibonacci(n-1)+Fibonacci(n-2)。 我们试着找出6的斐波那契,其会产生如下函数调用: image.png

当然,我们也可以用非递归的方式实现斐波那契函数

function fib(num){
    var n1 = 1,
        n2 = 1,
        n = 1;
    for(var i=3;i<=num;i++){
        n = n1 + n2;
        n1 = n2;
        n2 = n;
    }
    return n;
};

递归更容易理解,代码量更少。但并不比普通函数更快,反而更慢。所以,我们用递归,通常是因为它更容易解决问题。