算法入门学习——第三章:递归、栈、调用栈(Python实现)

第三章 递归

3.1 递归

引例:
假设你发现了一个上锁的箱子和一个盒子。钥匙可能在盒子里面,这个盒子里还有盒子,而盒子里的盒子又有盒子…(钥匙就在某个盒子中)为了找到钥匙,你将使用什么算法?
第1种方法:
1.创建一个要查找的盒子堆。
2.从盒子堆取出一个盒子,在里面找。
3.如果找到的是盒子,就会将其加入盒子堆中,以便以后再查找。
4.如果找到钥匙,则大功告成。
5.回到第2步。
伪代码:

def look_for_key(main_box):
    pile=main_box.make_a_pile_to_look_through()
    while pile is not empty:#盒子堆
        box=pile.grab_a_box()#取一个盒子
        for item in box:#遍历盒子
            if item.is_a_box():#是盒子就添加在盒子堆中
                pile.append(item)
            elif item.is_a_key():#找到钥匙结束
                print:"find key!"

第2种方法:
1.检查盒子中的每样东西。
2.如果是盒子就回到第1步。
3.如果是钥匙就大功告成。
伪代码:

def look_for_key(box):
    for item in box:
        if item.is_a_box():
            look_for_key(item)#递归函数调用自己
        elif item.is_a_key():
            print: "find key!"

在你看来哪一种方法更容易呢?
第1种方法是使用while循环,只要盒子堆不空就从中取出一个盒子,并在其中仔细查找,如下:
第2种方法是使用递归函数调用自己这种方法的伪代码,如下:

总结:这两种方法的作用相同。递归只是让解决方案更清晰,并没有性能上的优势,实际上在有些情况下使用循环的性能更好。使用循环程序的性能可能更高,使用递归程序可能更容易理解。
PS:很多算法都使用了递归,因此理解这种概念很重要。

3.2 基线条件和递归条件

编写递归函数时必须告诉它何时停止递归。正因如此,每个递归函数都有两部分:基线条件和递归条件
递归条件是指函数调用自己。
基线条件则指函数不再调用自己,从而避免形成无限循环。

3.3 栈

引例:
假设你去野外烧烤,并为此创建了一个待办事项清单——一叠便条(在讨论数组和链表时,也有一个待办事项清单,你可将待办事项添加到清单的任何地方,还可以删除任何一个待办事项)
一叠便条要简单的多——插入的待办事项放在清单的最前面;读取待办事项时,你只读取最上面那个,并将其删除。
因此这个待办事项清单只有两种操作:压入(插入)和弹出(删除并读取)

这种数据结构称为(请结合上述例子具象理解)。栈是一种简单的数据结构,刚才我们一直在使用它,却没有意识到。

栈的定义:
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

3.4 调用栈

引例:
计算机在内部使用被称为调用栈的栈,为了理解调用栈,来看一个函数:

def greet(name):
    print:"hello," + name
    greet2(name)
    print:"getting ready to say bye..."
    bye()
def greet2(name):
    print:"how are you ?"+name
def bye():
    print:"ok bye!"

下面详细介绍调用函数时发生的情况:
假如你调用greet(“Marry”)计算机将首先为该函数调用分配一块内存。

然后,我们来使用这些内存变量name被设置为Marry,这需要储存到内存中。

每当你调用函数时,计算机都像这样将函数调用涉及的所有变量的值储存到内存中。接下来你打印:hello Marry!

再调用greet2(“Marry”) 同样,计算机也为这个函数调用分配一块内存。

计算机使用一个栈来表示这些内存块,其中第2个内存块(greet2)位于第1个内存块(greet1)上面,你打印:how are you,Marry?**

然后从函数调用返回。此时站顶的内存块被弹出,现在占点的内存块是函数greet的,这意味着你返回到了函数greet。当你调用函数greet2时 ,函数greet指只执行了的一部分。

这是一个重要的概念!调用另一个函数时,当前函数暂停并处于未完成状态,该函数所有变量的值都还在内存中

执行完函数greet2后,你回到函数greet并从离开的地方接着往下执行,首先打印:getting ready to say bye…

再调用函数bye(),在栈顶添加了函数bye()的内存块,然后你打印:OK,bye!并从这个函数返回

现在你又回到了函数greet,由于没有别的事情要做,你就从函数greet返回这个栈。
因此,用于储存多个函数的变量成为调用栈。

3.5 递归调用栈

递归函数也使用调用栈。一起来看看递归函数f的调用栈。
注:f(5)写作5!(其实就是5的阶乘)下面是计算阶乘的递归函数:

def f(x):
    if x==1:
        return 1
    else:
        return x*f(x-1)

下面来详细分析调用f(3)时调用栈是如何变化的(别忘了栈顶的方框指出了当前执行到了什么地方):

注意:每一个f调用都有自己的x变量。在一个函数调用中不能访问另一个x变量——只能访问当前处在栈顶位置的变量。
请添加图片描述

总结:栈在递归中扮演着重要的角色,在本章开头的事例中,有两种寻找钥匙的办法。
使用第1种方法时,你创建一个带查找的盒子堆,因此你始终知道还有哪些盒子是需要查找的。
使用第2种方法及递归的方法时,没有盒子堆。既然没有盒子堆,那么算法怎么知道还有哪些盒子需要查找呢?原来盒子堆储存在了栈中,这个栈包含未完成的函数调用,每个函数调用都包括还未检查完的盒子。
使用栈很方便,因为你无需跟踪盒子堆栈替你这样做了。
最后,使用栈虽然很方便,但是也要付出代价,储存详尽的信息,可能占用大量的内存,每个函数调用都要占用一定的内存,如果占很高就意味着计算机储存了大量函数调用的信息。
在这种情况下你有两种选择:
一、重新编写代码,转而使用循环。
二、使用尾递归,这是一个高级的递归主题,不在我们讨论的范围内。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python函数:递归求Fibonacci数列 Fibonacci数列是一个非常经典的数列,它的定义如下: F() = F(1) = 1 F(n) = F(n-1) + F(n-2) (n>=2) 也就是说,Fibonacci数列的第n项是前两项的和。下面是使用递归方法求解Fibonacci数列的Python函数: def fibonacci(n): if n <= 1: return n else: return fibonacci(n-1) + fibonacci(n-2) 这个函数的实现非常简单,它首先判断n是否小于等于1,如果是的话,直接返回n。否则,它会递归调用自己来计算前两项的和,最终得到第n项的值。 需要注意的是,由于递归调用会导致函数的调用不断增加,因此在计算较大的Fibonacci数列时,这种方法可能会导致溢出。因此,如果需要计算较大的Fibonacci数列,建议使用其他方法,比如迭代或矩阵快速幂等算法。 ### 回答2: Python是一种非常适合递归操作的编程语言,可以轻松地实现递归函数。其中,递归求Fabonacci数列就是一种功能强大的实现方法,许多开发人员都喜欢使用这种方法。 在Python中,我们可以使用递归函数来实现Fabonacci数列的计算。所谓递归是指一个可以调用自身的函数,这样的函数就被称为递归函数。因此,一个递归函数可以通过调用自身来实现问题的求解。 要实现递归求Fabonacci数列,我们可以按照以下步骤: 1.定义一个递归函数fib(n),其中n是要求的Fabonacci数列的项数。 2.判断边界条件,即当n=0或n=1时,返回相应的值。 3.当n大于1时,递归调用fib函数求解前两项的和,并返回结果。 代码如下: ``` def fib(n): if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) ``` 接下来,我们可以使用这个函数来计算前n项的Fabonacci数列。例如,要计算前10项的Fabonacci数列,我们可以编写代码如下: ``` for i in range(10): print(fib(i)) ``` 这样,就可以输出前10项的Fabonacci数列了。值得注意的是,递归求解Fabonacci数列的计算量非常大,如果计算量太大,程序可能会崩溃。因此,我们需要在使用时注意计算量的控制,避免过度递归造成程序崩溃。 总之,递归求Fabonacci数列是Python编程中的一个非常实用的操作,可以方便地解决许多编程问题。如果你正在学习Python编程,建议你一定要掌握递归求Fabonacci数列的知识。 ### 回答3: fibonacci数列,又称黄金分割数列,通项公式为:F(n)=F(n-1)+F(n-2),其中F(0)=0,F(1)=1。 递归函数是指在函数的定义中使用函数自身的方法,这种方法常用于解决规模相对较小的问题,它可以简化程序的编写过程,但同时可能会占用较大的内存空间。 下面是递归求解fibonacci数列的代码示例: ```python def fibonacci(n): if n == 0 or n == 1: return n else: return fibonacci(n-1) + fibonacci(n-2) ``` 上面的代码中,如果输入的n等于0或1,直接返回n的值,否则返回第n个fibonacci数列的值。 在程序中,我们还可以使用列表的方式来递归求解fibonacci数列,这种方法可以避免重复计算,提高程序的效率。代码示例如下: ```python def fibonacci(n, cache={}): if n in cache: return cache[n] elif n <= 1: return n else: cache[n] = fibonacci(n-1, cache) + fibonacci(n-2, cache) return cache[n] ``` 上面的代码中,使用了字典类型的cache来存储每个n对应的fibonacci数列的值,如果已经计算过,则直接返回cache中的值,否则将计算结果存入cache中,然后返回。 需要注意的是,递归函数在求解大规模数据时,可能会导致递归次数过多,从而导致溢出的错误,因此我们需要考虑使用循环或其他算法来解决这个问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值