如果想更好的理解本文章,你可以看往期文章:
算法图解第二章–选择排序法(数组,链表的进一步理解)(学习笔记)
def countdown():
print i
countdown(i-1)
这个代码会出现一直运行的情况(你可以按Ctrl+C停止运行),之所以会出现这样的情况是因为没有告诉它何时停止递归,所以每个递归函数都应该包含两部分:基线条件和递归条件,去避免形成无限循环。改良后的代码:
def countdown():
print i
if i<=1:
return
else:
countdown(i-1)
大部分人学函数时就了解了递归,诸如的经典例子便是汉诺塔问题,如果想要了解的点击下面:
用递归来解决汉诺塔问题(超详细的个人解读)(Python)
我们先来理解一个重要的编程概念——调用栈(call stack)。
调用栈不仅对编程来说很重要,使用递归时也必须理解这个概念。
那什么是栈呢?
(如果想要全本电子书可以去我的主页里简介获取)
这是书上的解释,可能你没有耐心去理解,我总结了一下,看图理解:
你可以假设这是一个只有一个出口,三面封闭的盒子。
你如果往里面放东西,先放进去的东西就会被后放进去的东西压在下面,而当你想要去拿出被压在下面东西的时候,你就必须先取出上面的东西(也就是你后放进去的东西).
其实书上的例子就好比你写了一堆标签,然后一个钉子把他们钉在了墙上,你就只能从外面一个一个拿(注意后面写的标签在外面,也就是先依次写一堆标签,然后反向依次处理)。
既然你都理解了“栈”,那么如何调用它呢?我们先写一段简单代码辅助理解。
def greet2(name):
print("你好!{}".format(name))
def bye(name):
print("再见!{}".format(name))
def greet(name):
print("Hello!{}".format(name))
greet2(name)
print("最近好吗?")
bye(name)
greet("maggie")
结果:
Hello!maggie
你好!maggie
最近好吗?
再见!maggie
下面来详细介绍函数运行情况(我觉得这本书这里写得很明白,就照着书说一下):
首先,你调用greet(“maggie”),计算机将首先为该函数调用分配一块内存。如图:
我们来使用这些内存。
每当你调用函数时,计算机都像这样将函数调用涉及的所有变量的值存储到内存中。
接下来,你打印 Hello!maggie,再调用greet2(“maggie”)。
计算机使用一个栈来表示这些内存块,其中第二个内存块位于第一个内存块上面。
你打印 你好!maggie,然后从函数调用返回。
此时,栈顶的内存块被弹出。
现在,栈顶的内存块是函数greet的,这意味着你返回到了函数greet。
当你调用函数greet2时,函数greet只执行了一部分。
这是本节的一个重要概念:调用另一个函数时,当前函数暂停并处于未完成状态。该函数的所有变量的值都还在内存中。执行完函数greet2后,你回到函数greet,并从离开的地方开始接着往下执行,
首先打印 最近好吗?,再调用函数bye。
在栈顶添加了函数bye的内存块。然后,你打印再见!maggie,并从这个函数返回。
现在你又回到了函数greet。由于没有别的事情要做,你就从函数greet返回。这个栈用于存储多个函数的变量,被称为调用栈。
先写一段阶乘代码辅助理解下:
def fact(x):
if x==1:
return 1
else:
return x*fact(x-1)
简单理解的话就是:
假如x=3,,先运行else语句,就会得到 xfact(x-1),此时x为3,也就是3fact(2),
fact(2)函数会运行else语句则返回xfact(x-1),此时x为2,也就是2fact(1),综合上一句就是32fact(1),
fact(1)函数会运行if 语句,则返回1,综上所述便是311=6,
其实递归的话就是只有运行到fact(1)语句才会运行基线条件,也只有运行了基线条件,递归才会结束,
然后反向依次返回(就是依次返回1,2,6)。
注意每个fact调用都有自己的x变量。在一个函数调用中不能访问另一个的x变量。
.这是我的理解,可能有一些不足,下面放上书上的理论理解:
再次注意:
每个fact调用都有自己的x变量。在一个函数调用中不能访问另一个的x变量。
其实学下来,你会发现,使用栈虽然很方便,但是也要付出代价
存储详尽的信息可能占用大量的内存。每个函数调用都要占用一定的内存
如果栈很高,就意味着计算机存储了大量函数调用的信息
所以递归只是让解决方案更清晰,并没有性能上的优势。
实际上,在有些情况下,使用循环的性能更好。