树形递归
线性递归每次调用最多进行一次递归调用。尾部递归每次调用最多有一个递归调用,并且它是最后计算的东西。
上图中,左边是线性递归,右边是树形递归。注意区分。
A Problem
def find_zero(lowest, highest, func):
"""Return a value v such that LOWEST <= v <= HIGHEST and
FUNC(v) == 0, or None if there is no such value.
Assumes that FUNC is a non-decreasing function from integers
to integers (that is, if a < b, then FUNC(a) <= FUNC(b)."""
if lowest > highest: # Base Case
return None
elif func(lowest) == 0: # Base Case
return lowest
else: # Inductive (Recursive) Case
return find_zero(lowest + 1, highest, func)
上述程序是尾部递归,可转换为循环结构。
# Equivalent iterative solution
while lowest <= highest:
if func(lowest) == 0:
return lowest
lowest += 1
# If we get here, returns None
Problem, Take 2
可以通过利用函数有非递减的属性使其更快。
def find_zero(lowest, highest, func):
...
if lowest > highest: # Base Case
return None
middle = (lowest + highest) // 2
if func(middle) == 0: # Base Case
return middle
elif func(middle) < 0: # Inductive Case
return find_zero(middle + 1, highest, func)
else: # Inductive Case
return find_zero(lowest, middle - 1, func)
二分法。上述程序是尾部递归(有两个调用,但是只有一个执行),可转换为循环结构。
# Equivalent iterative solution
while lowest <= highest:
middle = (lowest + highest) // 2
if func(middle) == 0:
return middle
elif func(middle) < 0:
lowest = middle + 1
else:
highest = middle - 1
Side Trip: Base Cases Without If
实现程序不使用 if 语句,使用 and、or。
def is_a_zero(lowest, highest, func):
"""Return true iff there is a value v such that LOWEST <= v <= HIGHEST
and FUNC(v) == 0. Assumes that FUNC is a non-decreasing function
from integers to integers."""
middle = (lowest + highest) // 2
return lowest <= highest \
and (func(middle) == 0 \
or (func(middle) < 0 and is_a_zero(middle + 1, highest, func))
or (func(middle) > 0 and is_a_zero(lowest, middle - 1, func)))
上述程序是线性调用,而不是尾部调用,因为执行is_a_zero(middle + 1, highest, func))
后仍有可能执行(func(middle) > 0
,递归调用就不是最后执行的了。
iff
表示当且仅当。优先级:and
> or
。
在Python中,一行的结束意味着语句的结束。想要在下一行继续上一行的语句有两种办法:反斜杠、括号。
def is_a_zero(lowest, highest, func):
"""Return true iff there is a value v such that LOWEST <= v <= HIGHEST
and FUNC(v) == 0. Assumes that FUNC is a non-decreasing function
from integers to integers."""
middle = (lowest + highest) // 2
return lowest <= highest \
and (func(middle) == 0 \
or (func(middle) < 0 and is_a_zero(middle + 1, highest, func))
or is_a_zero(lowest, middle - 1, func)))
经修改,上述程序是树形递归。
寻路问题
考虑在迷宫中找到出路(通往底层的路径)的问题:
从给定的起始方块(最上面一行的某一个)开始,只要移动到的方块未被占用,就可以向下移动一行,并可以选择向左或向右移动一列。
def is_path(blocked, x0, y0):
"""True iff there is a path of squares from (X0, Y0) to some
square (x1, 0) such that all squares on the path (including first and
last) are unoccupied. BLOCKED is a predicate such that BLOCKED(x, y)
is true iff the grid square at (x, y) is occupied or off the edge.
Each step of a path goes down one row and 1 or 0 columns left or right."""
if blocked(x0, y0):
return False
elif y0 == 0:
return True
else:
return is_path(blocked, x0-1, y0-1) \
or is_path(blocked, x0, y0-1) \
or is_path(blocked, x0+1, y0-1)
数出有几条路径
例如,num paths(M2, 5, 6) 的结果是5。
def num paths(blocked, x0, y0):
"""Return the number of unoccupied paths that run from (X0, Y0)
to some square (x1, 0). BLOCKED is a predicate such that BLOCKED(x, y)
is true iff the grid square at (x, y) is occupied or off the edge. """
if blocked(x0, y0):
return 0
elif y0 == 0:
return 1
else:
return num paths(blocked, x0-1, y0-1) \
+ num paths(blocked, x0, y0-1) \
+ num paths(blocked, x0+1, y0-1)
增加方向
现在不仅可以向左下方、正下方、右下方移动,还可以向左侧、右侧。原先对三个方块进行递归调用(x0 − 1, y0 − 1),(x0, y0 − 1),(x0 − 1, y0 + 1)
,现在添加了对其他两个方块的调用(x0 − 1, y0) and (x0 + 1, y0)
。
会发生什么?无限递归,像(6,2)->(7,2)->(6,2)
。
时间分析
在某种意义上,所有的线性递归花费的时间都与问题的规模成正比。每次调用都可以产生另外三个调用,最多 y0 个子递归。这意味着可能的调用次数可能多达 3 ** y0——呈指数级增长。显然指数级增长是不好的。
分区计数问题
def num partitions(n, k):
"""Returns number of distinct ways to express N as a sum of positive
integers each of which is <= K, where K > 0. (Empty sum is 0.)"""
考虑num partitions(6, 3):
用 k 以内的整数对 n 进行分割的方法的数量等于:
- 使用不超过 m 的整数划分 n-m 的方法数
- 使用不超过 m-1 的整数划分 n 的方法数
def num partitions(n, k):
"""Number of distinct ways to express N as a sum of positive
integers each of which is <= K, where K > 0. (The empty sum is 0.)"""
if n == 0:
return 1
elif n < 0:
return 0
elif k == 0:
return 0
else:
return count_partitions(n-k, k) + count_partitions(n, k-1)
斐波那契数列
划分问题是数学递归关系的典型例子,另一个相似的例子是斐波那契数列:
def fib(n):
if n == 0 or n == 1:
return 1
else:
return fib(n-2) + fib(n-1)
同样,这是一个树状递归,需要指数级的计算量。