递归(recursion):通过将大问题分解成几个相对简单的且解法相同或类似的子问题,来将问题解决的方式。
10.1 递归函数
一个可以调用自身的函数称为递归函数(recursive function)。
递归调用(recursive calls)
递归展开(recursive unwinding)
10.2 递归的性质
递归需满足如下条件:
- 递归解决方法必须要有一个终止条件(base case);
- 递归解决方法必须要有一个递归条件(recursive case);
- 递归解决方法必须逐步向终止条件接近。
#-*-coding: utf-8-*-
# 阶乘
def fact(n):
assert n >= 0, "Factorial not defined for negative values."
if n < 2:
return 1
else:
return n * fact(n-1)
10.2.2 递归调用树
常用递归树(recursive call tree)来表示一个递归函数的调用过程。
10.2.3 Fibonacci序列
#-*-coding: utf-8-*-
# Fibonacci序列
def fib(n):
assert n >= 0, "Fibonacci not defined for n < 0."
if n == 1 or n == 0:
return 1
else:
return fib(n-1) + fib(n-2)
10.3 递归的原理
10.3.1 运行时栈(run time stack)
活动记录(activation record):每一次调用一个函数之后,会自动生成,以保存该函数的相关信息。
返回地址(return address):这些信息中就包括了返回地址。它是当函数执行完成时,下一条将要被执行指令的位置。
每次调用一个函数,都会产生一条活动记录,这些活动记录会自动被储存在运行时栈(run time stack)中,当函数执行完成时,这些活动记录被自动弹出,并销毁。
当递归函数函数时,运行时栈中的活动记录就可用于回溯或返回到递归函数完成执行后的下一条指令的位置。
以阶乘函数为例:
>>> def main():
... y = fact(2)
>>> main()
当main()函数执行时,第一条活动记录产生并被压入运行时栈中,之后是递归函数fact(),一直到n=0这一终止条件,这些活动记录开始逐个从运行时栈中弹出。
考虑反向打印出单链表中的元素。
蛮力法,其时间复杂度是O(n²)。
#-*-coding: utf-8-*-
# 使用蛮力法反向打印单链表中的元素
def printListBF(head):
# 先计数
numNodes = 0
curNode = head
while curNode is not None:
curNode = curNode.next
numNodes += 1
# 再遍历,第一次遍历,打印出最后一个元素,第二次遍历,打印出倒数第二个元素,依次类推
for i in range(numNodes):
curNode = head
for j in range(numNodes - i):
curNode = curNode.next
print curNode.data
使用栈,其时间复杂度是O(n)。
#-*-coding: utf-8-*-
# 使用栈来反向打印单链表中的元素,非递归
from lliststack import Stack
def printListStack(head):
s = Stack()
# 先遍历一次链表,将元素依次压入栈中
curNode = head
while curNode is not None:
s.push(curNode.data)
curNode = curNode.next
# 再将栈中元素弹出,并打印
while not s.isEmpty():
item = s.pop()
print item
使用递归,这是基于一个链表可以看成是连接与上一个结点的结点。
#-*-coding: utf-8-*-
# 使用递归反向打印单链表中的元素
def printList(node):
if node is not None:
printList(node.next)
print node.data
其运行时栈如下:
10.3.3 尾递归
如果一个函数的递归形式的调用都初现在函数的末尾,则称为尾递归(tail recursion)。
将之前的代码改为:
#-*-coding: utf-8-*-
# 尾递归
def printInorder(node):
if node is not None:
print node.data
printInorder(node.next)
在先前,我们是先将元素压入栈中,再执行print。在这里,就不需要将元素全部压入栈中,再执行print,此时可以使用迭代来代替递归。