美文网首页
继尾递归之后的优化方案

继尾递归之后的优化方案

作者: nomooo | 来源:发表于2020-01-10 14:26 被阅读0次

很多浏览器引擎并没有支持尾递归调用优化,即便支持,也要求代码运行环境在 strict mode 下

举个fibonacci 数列求和的例子

            const fibonacci = n => {
                if (n === 0) return 0
                if (n === 1) return 1
                return fibonacci(n - 1) + fibonacci(n - 2)
            }

fibonacci 数列求和非常耗费内存,如果用尾递归进行优化:

            const fibonacciTail = (n, a = 0, b = 1) => {
                if (n === 0) return a
                return fibonacciTail(n - 1, b, a + b)
            }

每次调用 fibonacciTail 函数后,会继续递归调用 fibonacciTail,函数的 n 会依次递减 1,它实际上是用来记录递归剩余求和的次数。而 a 和 b 两个参数在每次递归时也会在计算后再次传入 fibonacciTail 函数,最终返回值为 a,a 是上一次 a + b 的结果。这样每次递归都不会增加调用栈的长度,只是更新当前的堆栈帧而已。也就避免了内存的浪费和爆栈的危险。

对于不支持尾递归调用优化的场景,一般有两个优化方案:

  • 1、将递归改为循环
            const fibonacciLoop = (n, a = 0, b = 1) => {
                while (n--) {
                    [a, b] = [b, a + b]
                }
                return a
            }
  • 2、蹦床函数

蹦床函数并没有实现真正的尾递归,它只是将整个执行过程拆散,还是类似循环的效果:每次产生一个结果,该结果将会对下一次执行产生影响,就像蹦床一样,越蹦越高。我们看蹦床函数接受一个函数作为参数,在蹦床函数内部执行这个函数,如果执行结果,也就是该函数的返回值还是一个函数,那么就继续执行。一直到返回值不再是一个函数时,我们返回最终的结果。

            const trampoline = func => {
                while (func && func instanceof Function) {
                    func = func()
                }
                return func
            }

            const fibonacciFunc = (n, a = 0, b = 1) => {
                if (n > 0) {
                    [a, b] = [b, a + b]

                    return fibonacciFunc.bind(null, n - 1, a, b)
                } else {
                    return a
                }
            }

使用时:

          trampoline(fibonacciFunc(9)) //34

这算是"奇淫技巧",并不是实现了真正的尾递归调用优化。

真正实现尾递归调用优化的方案:
            const tailCallopt = func => {
                let result
                let started = false

                const accumulated = []
                return function asscumlator() {
                    accumulated.push(arguments)
                    if (!started) {
                        started = true
                    }
                    while (accumulated.length) {
                        result = func.apply(this, accumulated.shift())
                    }

                    started = false

                    return result
                }
            }

            const fibonacciTailOpt = tailCallopt(function(n, a = 0, b = 1) {
                if (n === 0) return a
                return fibonacciTailOpt(n - 1, b, a + b)
            })

            fibonacciTailOpt(9) //34

tailCallOpt 接受一个待优化的函数 func,返回一个新的 accumulator 函数。执行 fibonacciTailOpt(9) 就是第一步执行 accumulator。

第一次执行 accumulator 时,先将参数推入 accumulated 数组当中,started 标记为 true。然后进入 while 循环,循环中执行待优化的 func 函数,func 这个函数执行过程中需要保证调用 tailCallOpt 函数的返回值,这里为 fibonacciTailOpt;第二次执行 accumulator,将新的参数加入 accumulated 数组;这样 accumulated 数组长度始终不为零,循环继续进行。

整个过程就是 accumulated 数组放进去一个参数,执行一次,得到结果,accumulated 清空;再放进去新的参数,执行得到结果,accumulated 再清空,以此类推。直到 func 返回了基本类型值(非函数值),这时候 accumulated 数组不会再有新的参数进来,因此返回最终结果。

end

你品,你细品

相关文章

  • 继尾递归之后的优化方案

    很多浏览器引擎并没有支持尾递归调用优化,即便支持,也要求代码运行环境在 strict mode 下 举个fibon...

  • Kotlin语言(九):特性

    1、尾递归优化 尾递归:函数在调用自己之后没有再执行其他任何操作就是尾递归 尾递归优化的原理就是将递归转换成迭代,...

  • 什么是尾调用?什么是尾递归?尾调用的优化?尾递归优化?

    尾调用优化 尾递归(尾调用优化)

  • 7.尾递归优化

    尾递归:最后一行调用自身之后没有任何操作直接返回kotlin尾递归优化,关键字tailrec如: 不优化的话大量的...

  • 第2模块第1章2829递归的作用尾递归优化

    尾递归优化 def cal(n): print(n) return cal(n+1) cal(1) 尾递归优化并不...

  • 尾递归优化

    “尾递归优化”的含义是:如果递归函数属于尾递归,那么运行时会优化其调用过程。优化主要针对调用栈,将多层调用,转化为...

  • 9. 递归函数

    使用递归函数需要注意防止栈溢出解决递归调用栈溢出的方法是通过尾递归优化遗憾的是,大多数编程语言没有针对尾递归做优化...

  • 递归优化-尾递归

    一、定义 在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。 二、利弊 递归函数...

  • 递归优化-尾递归

    尾递归能否起到优化作用跟编译器有关系,并不是用了尾递归就一定能起到优化作用。 定义:函数里的最后一个动作是返回一个...

  • 递归的优化--尾递归

    https://blog.csdn.net/zcyzsy/article/details/77151709

网友评论

      本文标题:继尾递归之后的优化方案

      本文链接:https://www.haomeiwen.com/subject/ahepactx.html