分治与递归(Recurrence)
如果为了描述问题的某一状态,需要用到它的上一状态;而描述上一状态,又必须用到它的再上一状态……这样用自已来定义自己的方法称为递归。
数学表达式:f(n) = n*f(n-1) (n>0)
直接或间接地调用自身的算法称为递归算法。用函数自身给出定义的函数称为递归函数。
两个要素
- 边界条件:边界条件是需要解决的问题的最简单的情况,比如,当n=0或n=1时,f(n)=1 —— 直接可以得到问题的答案,不需要使用f(n-1)进行递归。同时,边界条件是递归停止的条件,如果没有边界条件,或没有进行合理设置边界条件,递归会进入死循环。
- 递归定义:递归定义是使问题向边界条件转化的规则。在设计递归定义的时候,必须能使问题越来越简单,比如,如果f(n)可以由f(n-1)来定义,那么不断地递归,整个方法会越来越靠近f(0)或f(1)的递归边界,从而获得最终的结果。
适用条件
- 数据的定义形式按递归定义,比如,裴波那契数列的定义: f(n)=f(n-1)+f(n-2); f(0)=1; f(1)=2。
- 数据之间的关系(即数据结构)按递归定义,比如,树的遍历, 图的搜索等。
- 问题解法按递归算法实现,比如,回溯法等。
优缺点
- 优点:结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此它为设计算法、调试程序带来很大方便。
- 缺点:递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。 缺点的解决方法:在递归算法中消除递归调用,使其转化为非递归算法 —— 后两种方法在时空复杂度上均有较大改善,但其适用范围有限。
- 采用一个用户定义的栈来模拟系统的递归调用工作栈。该方法通用性强,但本质上还是递归,只不过人工做了本来由编译器做的事情,优化效果不明显。
- 用递推来实现递归函数。
- 通过Cooper变换、反演变换能将一些递归转化为尾递归,从而迭代求出结果。
实例 Python 3
计算数的阶乘
def factorial(n: int) -> int: """ 当n>=0时,对n进行阶乘计算。 阶乘函数可以定义为: n! = | 1 n = 0 | n * (n-1)! n > 0 | -1 n < 0 :param n: :return: """ if n < 0: return -1 else: if n == 0: return 1 # 边界条件 else: return n * factorial(n - 1) # 递归方程
计算斐波那契数列的第n个数
def fibonacci(n: int) -> int: """ 当n>=0时,无穷数列1,1,2,3,5,8,13,21,34,55,...,称为Fibonacci数列。 Fibonacci数列的第n个数可以定义为: f(n) = | 1 n = 0 | 1 n = 1 | f(n-1) + f(n-2) n > 0 | -1 n < 0 :param n: :return: """ if n < 0: return -1 else: if n == 0: return 1 # 边界条件 elif n == 1: return 1 # 边界条件 else: return fibonacci(n-1) + fibonacci(n-2) # 递归方程
分治和递归的区别是什么?
分治是一种思想,并不涉及到具体的算法。在大多数情况下,分治的思想需要借助递归算法来实现。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。
简单的说:分治法就是把1个分为多个,递归法就是把多个归一的解决问题方法。
在知乎上看到的一种理解:
递归只是分治法的一个特例, 宽泛点说, 只要是自顶向下通过分解问题求解的思维方式, 都算是分治法, 分治法是一种求解思想. 具体实现形式各种各样的都有. 我喜欢把分治法比喻成搭积木, 先看手上有没有合适的现成的积木, 如果没有, 那我就去为当前问题量身定制造一块积木, 如果有了这样一块积木问题解决起来会变得容易, 那就先去造这块积木, 分治法大致就是这么个感觉.