递归是很多算法都使用的一种编程方法。听说递归是一种十分优雅的问题解决办法,可是对于初涉递归的我,还没有形成这种独特的体会。
学习使用递归的关键在于:如何将问题分为基线条件和递归条件。
基线条件和递归条件
由于递归函数调用自己,因此编写这样的函数时很容易出错,进而导致无限循环。
例如下面这个函数:
def countdown(i):
"""倒计时"""
print (i)
countdown(i-1)
假设i的初始值为3,运行上述代码后:
3, 2, 1, 0, -1, -2, .........
它会一直运行下去,(可按Ctrl+C停止)
所以,编写递归函数必须要让函数能在某个时候停止递归。
让递归函数停止递归的条件就是基线条件。
递归条件指函数调用自己;基线条件指函数不再调用自己。
现在我们给函数countdown添加基线条件:
def count_down(i):
"""倒计时"""
print(i)
if i <= 1: # 基线条件(指函数不再调用自己)
return
else: # 递归条件(指函数调用自己)
count_down(i-1)
count_down(3)
运行结果:
3
2
1
栈
使用递归必须就要理解一个名为“调用栈”的编程概念。因为递归函数在运行的过程中是存储在栈中的。
栈是一种数据结构,只有两种基本操作:压入(进栈)和弹出(出栈)。且遵循后进先出的规则。
计算机在内部使用的栈被称为调用栈。下面用一个函数来看看计算机如何使用调用栈:
def greet(name):
"""问候1"""
# greet函数问候用户,再调用了另外两个函数
print("hello " + name + " !")
greet2(name)
print("getting reading to say bye...")
bye()
这个函数问候用户,再调用了另外两个函数,这两个函数的代码如下:
def greet2(name):
"""问候2"""
print("how are you, " + name + " ?")
def bye():
"""拜拜"""
print("ok, bye!")
注释:在python中,print也是一个函数,但我们先暂且不考虑它。
假设我们调用greet("you")。计算机先为其分配一块内存:
接下来,打印出 hello you !
。再调用函数greet2("you")。同样,计算机也为这个函数调用分配一个内存块:
然后打印出 how are you, you ?
。然后从函数调用返回。此时,栈顶的内存块被弹出:
执行完函数greet2后,回到函数greet,并从离开的地方接着往下执行:首先打印 getting ready to say bye...
。再调用函数bye:
然后打印 ok bye !
。并从这个函数返回。
现在又回到了函数greet。由于没有别的事要做,就从函数greet返回。这个被用于存储多个函数变量的栈,称之为调用栈。
递归调用栈的另一个应用就是计算阶乘。下面是一个计算阶乘的递归函数:
def fact(x):
"""计算阶乘的函数"""
if x == 1:
return 1
else:
return x * fact(x-1)
我们来分析一下调用fact(3)时,调用栈是如何变化的:
说明:
使用递归不能提高程序的性能,它只是让程序更容易理解。
使用栈很方便,但会占据很多的内存
尾递归
最后介绍一个尾递归。尾递归是一种高级递归,它和普通递归函数的区别在于:尾递归在函数执行的最后一步调用自身,而其他递归函数在函数的最后一步不仅调用了自身,还掺杂着其他表达式。
举个例子:
计算阶乘:
def fact(x):
"""计算阶乘的函数"""
if x == 1:
return 1
else:
return x * fact(x-1)
这不是尾递归函数。
def fact(x):
"""计算阶乘的函数"""
if x == 1:
return 1
else:
return fact(x-1)
这就是尾递归函数。
小结
递归指调用自己的函数
每个递归函数都有两个条件:基线条件和递归条件
栈有两种操作:压入和弹出
所有的函数调用都进入调用栈
每天学习一点点,每天进步一点点。