【一】递归定义与延伸
先给出递归的定义
递归算法是一种直接或者间接调用自身函数或者方法的算法。
递归是计算机科学的精髓,这一点在吴军老师的《Google方法论》专栏也有同样的阐述,他认为递归是计算机思维和人思维最大的不同。
我们常用的正向思维成为递推,而递归可以说是一种逆向思维。举例来说,计算一个数的阶乘,比如5 !,我们惯用的递推思维一定是 1x2x3x4x5 ,那么计算机的递归思想会如何计算呢?正好与人类相反,比如5 !,它会把5 !拆分成5x4 !,再把4 !拆分成 4x3 !,直到1 !=1 ,也就是达到了终止条件;接下来要做的,就是倒推回所有的结果。所以总结来说,递归的过程是 自顶向下设计,自下而上回归。
那么计算机为什么要采用这种与人类思维完全相反的方式呢?
因为算法逻辑非常简单。递归过程的每一步用的都是同一个算法,计算机要做的,就是自顶向下不断重复。
递归方法具有如下的特点,为了便于记忆,我把它总结为 可分解,自重复,有出口:
- 可分解:一个问题的解可以分解为几个子问题的解
- 自重复:这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
- 有出口:存在递归终止条件
其实归纳来说,递归的本质有两条:自顶而下,自己不断重复。
【二】递归代码如何写
说了这么半天大道理,那么我们如何写递归代码呢?
- 找到将大问题分解为小问题的规律
- 基于规律写出递推公式
- 推敲终止条件
- 将递推公式和终止条件翻译成代码
写递归代码的注意事项:
- 避免堆栈溢出
- 避免重复计算
【三】递归的应用场景
-
斐波那契数列
F(1)=1,F(2)=1,F(n) = F(n-1) + F(n-2)(n>=3,n∈N*)
public class FibonacciSequence { public static void main(String[] args){ System.out.println(Fribonacci(9)); } public static int Fribonacci(int n){ if(n <= 2) return 1; else return Fribonacci(n-1)+Fribonacci(n-2); } }
-
前中后序二叉树遍历
/** * 递归前序遍历 */ public static void ProOrder(TreeNode tree) { System.out.println(tree.value); if (tree.left != null) { ProOrder(tree.left); } if (tree.right != null) { ProOrder(tree.right); } } /** * 递归中序遍历 */ public static void InOrder(TreeNode tree) { if (tree.left != null) { InOrder(tree.left); } System.out.println(tree.value); if (tree.right != null) { InOrder(tree.right); } } /** * 递归后顺遍历 */ public static void PostOrder(TreeNode tree) { if (tree.left != null) { PostOrder(tree.left); } if (tree.right != null) { PostOrder(tree.right); } System.out.println(tree.value); }
-
汉诺塔问题
-
DFS深度优先搜索
-
N皇后问题
【四】总结思考
其实从递归的思想中,我们可以受到启发:
人类的认识受到我们生活空间的局限,因此习惯于从小到大渐渐扩展的思维方式。而计算机因为可以直接处理大场景,因此有时会采用从大到小,层层分解的递归方法。这可以说给我们提供了一个新的看问题的方法和解决问题的思路。