zoukankan      html  css  js  c++  java
  • scala-尾递归

    ------------------------- by chenkh -----------------------------

    随笔记录什么是尾递归,为什么需要尾递归,尾递归show by example。

    0,前言

      递归通过灵巧的函数定义,告诉计算机做什么。在函数式编程中,随处可见递归思想的运用。一个递归的经典例子:

    //递归快排
      def quicksort(ls: List[Int]):List[Int] = {
        if(ls.isEmpty)
          ls
        else
          quicksort(ls.filter(_ < ls.head)) ::: ls.head :: quicksort(ls.filter(_ > ls.head))
        //quicksort(ls.filter(x => x < ls.head)) ::: ls.head :: quicksort(ls.filter(x => x > ls.head))
      }
    

      我们以上面代码最后一个快速排序函数为例,使用递归的方式,其代码实现非常的简洁和通俗易懂。递归函数的核心是设计好递归表达式,并且确定算法的边界条件。上面的快速排序中,认为空列表就是排好序的列表,这就是递归的边界条件,这个条件是递归终止的标志。

    1,什么是尾递归?

      尾递归本质上仍然是一种递归,可以看做是普通递归方法的改进。

      尾递归是指递归调用是函数的最后一个语句,而且其结果被直接返回,这是一类特殊的递归调用。由于递归结果总是直接返回, 尾递归比较方便转换为循环 ,因此编译器容易对它进行优化。

    2,为什么需要尾递归?

      递归算法需要保持调用堆栈,效率较低,如果调用次数较多,会耗尽内存或栈溢出。然而,尾递归可以克服这一缺点。

    3,show by factorial

    //递归阶乘
      def factorial(n: BigInt): BigInt = {
        if(n <=1) 1 else n * factorial(n-1)
      }
      //尾递归阶乘
      def factorialTailRecursive(n: BigInt): BigInt = {
        def _loop(acc: BigInt, n: BigInt): BigInt = if(n <=1) acc else _loop(acc*n, n-1)
        _loop(1, n)
      }
    /*
      我们发现,由于每次递归调用n-1的阶乘时,都有一次额外的乘法计算,这使得堆栈中的数据都需要保留。在新的递归中要分配新的函数栈。
      factorial(4)
      --------------
      4 * factorial(3)
      4 * (3 * factorial(2))
      4 * (3 * (2 * factorial(1)))
      4 * (3 * (2 * 1))
      而尾递归在效率上和循环是等价的,该函数中的 _loop 在最后一步,要么返回递归边界条件的值,要么调用递归函数本身。
      factorialTailRecursive(4)
      --------------------------
      _loop(1, 4)
      _loop(4, 3)
      _loop(12, 2)
      _loop(24, 1)
       */
    

      

      改写成尾递归版本的关键:

      尾递归版本最重要的就是找到合适的累加器,该累加器可以保留最后一次递归调用留在堆栈中的数据,积累之前调用的结果,这样堆栈数据就可以被丢弃,当前的函数栈可以被重复利用。

      在这个例子中,变量acc就是累加器,每次递归调用都会更新该变量,直到递归边界条件满足时返回该值。

      对于尾递归,Scala语言特别增加了一个注释 @tailrec ,该注释可以确保程序员写出的程序是正确的尾递归程序,如果由于疏忽大意,写出的不是一个尾递归程序,则编译器会报告一个编译错误,提醒程序员修改自己的代码。

      

  • 相关阅读:
    GitHub上创建项目
    html5的标签中,哪些是行内元素,哪些是块级元素。
    盒子模型及其他的层次结构关系
    二维码生成
    SSH整合笔记
    Spring回顾
    struts2 测试错题解析
    Java Script基础
    Java OOP考试错题分析
    接口的用法及注意点
  • 原文地址:https://www.cnblogs.com/chen-kh/p/6198148.html
Copyright © 2011-2022 走看看