什么是递归
前面我们对一些算法的复杂度进行了分析,但这些都是基于循环和迭代的,这一节我们会对递归的算法进行复杂度分析。首先要需要知道什么是递归,递归(recursion)是函数调用自身的一个过程。举个例子,假设你是一个英语水平有限的人,你在读一段英文材料中遇到了某个生词,你需要查字典去了解这个单词的意思,但是只给你了英英字典,意味着你要查找的单词下方的释义也有可能是你不认识的单词,于是你又继续去查找那些你不认识的单词,而那些单词的释义有可能又出现你不认识的单词,你又继续查找,如此往复,直到你能够全部看懂为止。这样一个过程我们可以把它看做是一个递归的过程,因为查字典这个动作在不停地调用自身。
试想如果你的英语水平很糟糕,你可能查了很久的字典还是没有弄明白原来那个生词是什么意思,甚至出现单词 A 用到了单词 B 做释义,单词 B 又用到了单词 A 做释义这种情况,即出现死循环。那么递归结束的条件是什么呢?显然,如果一个单词的释义你全都能看懂,那就意味着当前递归就结束了,我们把这种条件叫做初始条件(initial condition),也叫递归出口。
阶乘的计算
阶乘大家应该都不陌生,一个数的阶乘
def factorial(n):
if n == 0:
return 1
return n * factorial(n-1)
用一个数学递推式是来表达上面的关系:
我们观察可以得到这样的规律:问题的规模由原来的
其中
于是我们得出计算阶乘的时间复杂度为
汉诺塔
汉诺塔大家肯定都玩过,规则也很简单:有 A, B, C 三根柱子,上面穿有从大到小排列的圆盘,现在你需要借助 B 柱把 A 柱上的圆盘挪到 C 柱上,一次只能挪一个,且大的圆盘不能放在小的圆盘的上面。
汉诺塔跟递归有什么关系呢?如果你是一个细心观察的人,你就会发现,要想把
我们用几张图来直观地理解一下,假设这里有 4 个圆盘,我们可以分为三个步骤:
- 步骤一:用某种方法将 3 个圆盘从 A 柱移动到 B 柱
- 步骤二:将 A 柱的圆盘移动到 C 柱
- 步骤三:再用某种方法将 3 个圆盘从 B 柱移动到 C 柱
那么怎样来写递归关系式呢?我们看到,要解决
我们还是采用回代的方式解出这个关系式:
复杂度为
def hanoi(a, b, c, n):
if n == 1:
print("{} -> {}".format(a, c))
return
hanoi(a, c, b, n-1) # c 柱为枢纽,将 a 柱中 n - 1 个圆盘移到 b 柱上
hanoi(a, b, c, 1) # 将待移动的圆盘数设为 1
hanoi(b, a, c, n-1) # a 柱为枢纽,将 b 柱中 n - 1 个圆盘移到 c 柱上
让我们试试n = 3
时输出的结果:
>>> hanoi(3)
... a -> c
... a -> b
... c -> b
... a -> c
... b -> a
... b -> c
... a -> c
→本节全部代码←
← 复杂度分析(非递归)| 算法与复杂度zhuanlan.zhihu.com