尾递归

eetCode刷题-递归篇

二次信任 提交于 2019-12-03 01:36:05
递归是算法学习中很基本也很常用的一种方法,但是对于初学者来说比较难以理解(PS:难点在于不断调用自身,产生多个返回值,理不清其返回值的具体顺序,以及最终的返回值到底是哪一个?)。因此,本文将选择LeetCode中一些比较经典的习题,通过简单测试实例,具体讲解递归的实现原理。本文要讲的内容包括以下几点: 理解递归的运行原理 求解递归算法的时间复杂度和空间复杂度 如何把递归用到解题中(寻找递推关系,或者递推公式) 记忆化操作 尾递归 剪枝操作 理解递归的运行原理 例1求解斐波那契数列 题目描述(题目序号:509,困难等级:简单): 求解代码(基础版): class Solution { public int fib(int N) { if(N <= 1) return N; return fib(N - 1) + fib(N - 2); } } 现在以N = 5为例,分析上述代码的运行原理,具体如下图: 递归的返回值很多,初学者很难理解最终的返回值是哪个,此时可以采用上图的方式画一个树形图,手动执行递归代码,树形图的叶节点即为递归的终止条件,树形图的根节点即为最终的返回值。树形图的所有节点个数即为递归程序得到最终返回值的总体运行次数,可以借此计算时间复杂度,这个问题会在后文讲解。 例2 二叉树的三种遍历方式 二叉树的遍历方式一般有四种:前序遍历、中序遍历、后序遍历和层次遍历

【基础】递归和尾递归,我的个人看法

匿名 (未验证) 提交于 2019-12-03 00:22:01
之前只知道递归这个概念,无非是函数内部调用自己。 递归递归,有来有回。从调用的最外层开始一层一层往里进去直到最里层然后带着结果一层层出来到最外面返回。 那么 尾递归是什么呢? 很多篇文章拿阶乘来举例子。 我按照我的理解总结为 最后的返回值只有函数自身的调用,而不含有表达式。 函数递归调用过程中产生的中间累计的值以变量的形式存储在下一层函数的入参中,逐级传递下去 。 第二句话是我独创的。 接下来我们用代码来看看递归和尾递归的区别 # 递归 def func (n) : if n == 1 : return 1 else : return n * func(n - 1 ) 我们来看看这样的函数的计算过程 => func(5) => 5 * fact(4) => 5 * (4 * fact(3)) => 5 * (4 * (3 * fact(2))) => 5 * (4 * (3 * (2 * fact(1)))) => 5 * (4 * (3 * (2 * 1))) => 5 * (4 * (3 * 2)) => 5 * (4 * 6) => 5 * 24 => 120 我们再看看尾递归的代码和流程 def func (n, src) : if n == 1 : return src return func(n - 1 , n * src) => func(5, 1) => func

Python3:函数

匿名 (未验证) 提交于 2019-12-02 22:51:30
# 函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”: a = abs # 变量a指向abs函数 print (a(- 1 )) # 所以也可以通过a调用abs函数 # 在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。 # 我们以自定义一个求绝对值的my_abs函数为例: # 如果没有return语句,函数执行完毕后也会返回结果,只是结果为None。return None可以简写为return。 def my_abs (x): if x >= 0 : return x else : return -x print (my_abs(- 99 )) # 如果想定义一个什么事也不做的空函数,可以用pass语句: # pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。 # pass还可以用在其他语句里 def nop (): pass # 让我们修改一下my_abs的定义,对参数类型做检查,只允许整数和浮点数类型的参数。数据类型检查可以用内置函数isinstance()实现: def my_abs (x): if not

Python尾递归优化

匿名 (未验证) 提交于 2019-12-02 22:51:30
Python开启尾递归优化 cpython本身不支持尾递归优化, 但是一个牛人想出的解决办法: 实现一个 tail_call_optimized 装饰器 #!/usr/bin/env python2.4 # This program shows off a python decorator( # which implements tail call optimization. It # does this by throwing an exception if it is # it's own grandparent, and catching such # exceptions to recall the stack. import sys class TailRecurseException: def __init__(self, args, kwargs): self.args = args self.kwargs = kwargs def tail_call_optimized(g): """ This function decorates a function with tail call optimization. It does this by throwing an exception if it is it's own grandparent, and

漫谈递归和迭代

亡梦爱人 提交于 2019-12-01 04:59:06
先讲个故事吧。 从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?“从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?‘从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?……’”。 这个故事永远也讲不完,因为没有递归结束条件。老师讲递归时总是说,递归很简单,一个递归结束条件,一个自己调用自己。如果递归没有结束条件,那么就会无限递归下去。在编程的时候,没有递归结束条件或者递归过深,一般会造成栈溢出。 下面这个函数,可以利用栈溢出来估测栈的大小: 1 void stack_size() 2 { 3 static int call_time = 0; 4 char dummy[1024*1024]; 5 call_time++; 6 printf("call time: %d\n",call_time); 7 stack_size(); 8 } 递归算法一般用于解决三类问题:这个函数定义了1M的局部变量,然后调用自己。栈溢出时会崩溃,根据最后打印出的数字可以算一下栈的大小。 (1)数据的定义是按递归定义的。(Fibonacci函数) (2)问题解法按递归算法实现。(回溯) (3)数据的结构形式是按递归定义的。(树的遍历,图的搜索) 对于求1+2+3+…+n这种问题,大部分人不会用递归方式求解: 1 2 3 4

python 递归函数

天涯浪子 提交于 2019-11-29 22:05:15
递归函数 阅读: 115067 在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。 举个例子,我们来计算阶乘 n! = 1 x 2 x 3 x ... x n ,用函数 fact(n) 表示,可以看出: fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n 所以, fact(n) 可以表示为 n x fact(n-1) ,只有n=1时需要特殊处理。 于是, fact(n) 用递归的方式写出来就是: def fact(n): if n==1: return 1 return n * fact(n - 1) 上面就是一个递归函数。可以试试: >>> fact(1) 1 >>> fact(5) 120 >>> fact(100) 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L 如果我们计算 fact(5) ,可以根据函数定义看到计算过程如下: ===> fact(5) ===> 5 * fact(4

斐波拉契数列

隐身守侯 提交于 2019-11-28 10:56:37
斐波拉契数列 (n > 2时) 第 n 项 // 递归写法 (性能极差, 首先这个不是纯尾递归,其次Chrome也没有做尾递归优化) function Fibonacci(n) { if (n<3) return 1; return Fibonacci(n-1) + Fibonacci(n-2); } // 循环写法 function Fibonacci2(n) { if (n<3) return 1; let first, second=1,third=1; for (let i = 3; i <= n; i ++) { first = second; second = third; third = first + second; } return third; } 来源: https://www.cnblogs.com/amiezhang/p/11405817.html

尾调用与尾递归优化

倖福魔咒の 提交于 2019-11-28 01:57:32
  〇、是为序  以前也看到过尾递归及其优化,但在当时并不完全能够理解,最近几天陆陆续续复习了一下《汇编语言》和《自己动手写操作系统》两本书,对于函数调用背后的栈机制有了更加清晰的理解,回过头来看尾递归就觉得容易理解多了。   就像 阮一峰 老师在下文中所写的那样,栈溢出在递归程序编写过程中是常会出现的错误,但有时把递归程序改写成非递归可能并非易事,此时考虑一下采用尾递归或者相关优化技术就非常有必要,虽然这篇文章是使用javascript为例写的,但对于C++等也完全适用,特转载此文以备忘。    以下转自http://www.ruanyifeng.com/blog/2015/04/tail-call.html   尾调用 (Tail Call)是函数式编程的一个重要概念,本文介绍它的含义和用法。 一、什么是尾调用?   尾调用的概念非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。 function f(x){ return g(x); }    上面代码中,函数f的最后一步是调用函数g,这就叫尾调用。 以下两种情况,都不属于尾调用。 // 情况一 function f(x){ let y = g(x); return y; } // 情况二 function f(x){ return g(x) + 1; }    上面代码中,情况一是调用函数g之后

尾调用优化和尾递归改写

安稳与你 提交于 2019-11-28 01:57:23
1 尾调用 尾调用就是指某个函数的最后一步是调用另一个函数。 # 是尾调用 def f(x): return g(x) # 不是尾调用,因为调用函数后还要执行加法,加法才是最后一步操作 def f(x): return 1+g(x) 2 尾调用优化 函数调用有一个调用栈,栈内保存了这个函数内部的变量信息。函数掉用就是切换不同的调用帧,从而保证每个函数有独立的运行环境。因为尾调用是函数的最后一步操作,所以在进入被尾调用函数之前并不需要保留外层函数的运行时环境,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。这就叫做"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义。 尾递归 如果尾调用自身,就称为尾递归。递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误。 def factorial(n) { if (n == 1) return 1; return n * factorial(n - 1); } factorial(5) // 120

尾调用和尾递归

佐手、 提交于 2019-11-28 01:57:03
尾调用 尾调用 (Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。 //尾调用 function f(x) {return g(x);} //上面代码中,函数f的最后一步是调用函数g,这就叫尾调用 //不属于尾调用举例 // 情况一 function f(x){ let y = g(x); return y; } // 情况二 function f(x){ return g(x) + 1; } // 情况三 function f(x){ g(x); } 尾调用优化 尾调用之所以与其他调用不同,就在于它的特殊的调用位置。 我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。 function f() { let m = 1; let n