递归函数优化--尾递归

本文介绍了递归函数的工作原理,特别是递归带来的内存消耗问题。接着,阐述了尾调用的概念,即函数的最后一步是调用另一个函数的情况。通过举例说明了哪些情况不属于尾调用。尾递归是指递归函数中的尾调用,它允许每次执行时调用帧只有一项,从而节省内存并避免栈溢出。最后,展示了将普通递归函数转换为尾递归的示例,以递归计算阶乘为例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

铺垫

函数的调用会在内存中形成一个调用记录,也称作“调用帧”,用来保存调用位置和内部变量等信息。内存中用一个栈结构来保存调用帧,也称“调用栈”。函数被调用时调用帧入栈,函数返回时调用帧弹栈。

当发生函数的嵌套时,如在A函数中调用B函数,就会有连续的两个调用帧入栈

function A(){
    ...
    B();
    ...
}

当B函数返回时,B调用帧弹栈,继续执行A函数,当A函数返回时,A调用帧弹栈

递归函数就是在函数内部不断调用自身,直到满足特定条件

递归函数的缺点

递归函数在函数内部不断调用自身,导致调用栈快速增大,这是很浪费内存资源的,而且调用栈都是有固定大小的,如果递归次数过多会造成栈溢出错误

递归函数优化--尾递归

什么是尾调用?

很简单,就是指某个函数的最后一步操作是调用另一个函数

//js
function A(x){
    ...
    return B(x);
}

以下三种都不属于尾调用

//情况一
function A(x){
  let y = B(x);
  return y;
}

// 情况二
function A(x){
  return B(x) + 1;
}

// 情况三
function A(x){
  B(x);
}

情况一:调用函数B之后有赋值操作

情况二:调用函数B之后有运算操作

情况三:函数调用如果没有显式的return,编译器会隐式的  return undefined;所以他的最后一步不是调用函数B,而是return undefined

我们再来看一看尾调用的调用栈情况

function A(x){
    ...
    return B(x);
}
A(1);

因为函数返回时调用帧会弹栈,所以A函数的最后一步操作 return B(x),这一行代码会让A调用帧弹栈,并且让B调用帧入栈,所以此时调用栈里只有一条记录。当把这一方法用到递归中时就是尾递归了

尾递归

如果保证所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这样就大大的节省了内存,而且不会出现栈溢出

接下来我们看看怎么把普通的递归函数改成尾递归,以递归求阶乘为例

普通递归函数

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) //120

尾递归

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

调用栈中只有一个调用帧,所以复杂度为O(1)

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值