不管是数据结构还是算法思维,它们的目标都是降低时间复杂度。数据结构是从数据组织形式的角度达成这个目标,而算法思维则是从数据处理的思路上去达成这个目标。
本文介绍算法思维基础之一 ----递归
1. 什么是递归
在数学与计算机科学中,递归 (Recursion))是指在函数的定义中使用函数自身的方法,直观上来看,就是某个函数自己调用自己,有循环的意味在里面。
递归有两层含义:
- 递归问题必须可以分解为若干个规模较小、与原问题形式相同的子问题。并且这些子问题可以用完全相同的解题思路来解决;
- 递归问题的演化过程是一个对原问题从大到小进行拆解的过程,并且会有一个明确的终点(临界点)。一旦原问题到达了这个临界点,就不用再往更小的问题上拆解了。最后,从这个临界点开始,把小问题的答案按照原路返回,原问题便得以解决。
格外重要的是,这个解决问题的函数必须有明确的结束条件,否则就会导致无限递归的情况。总结起来,递归的实现包含了两个部分,一个是递归主体,另一个是终止条件。
2. 递归的算法思想
递归的数学模型其实就是数学归纳法,这个证明方法是我们高中时期解决数列问题最常用的方法。接下来先通过一道题目简单回顾一下数学归纳法。
一个常见的题目是:证明当 n 等于任意一个自然数时某命题成立。
当采用数学归纳法时,证明分为以下 2 个步骤:
证明当 n = 1 时命题成立;
假设 n = m 时命题成立,那么尝试推导出在 n = m + 1 时命题也成立。
与数学归纳法类似,当采用递归算法解决问题时,我们也需要围绕这 2 个步骤去做文章:
当你面对一个大规模问题时,如何把它分解为几个小规模的同样的问题;
当你把问题通过多轮分解后,最终的结果,也就是终止条件如何定义。
所以当一个问题同时满足以下 2 个条件时,就可以使用递归的方法求解:
1. 可以拆解为除了数据规模以外,求解思路完全相同的子问题;
2. 存在终止条件。
在我们讲述树结构时,曾经用过递归去实现树的遍历。接下来,我们围绕中序遍历,再来看看递归在其中的作用。
对树中的任意结点来说,先中序遍历它的左子树,然后打印这个结点,最后中序遍历它的右子树。可见,中序遍历是这样的一个问题,如下图所示:
当某个结点没有左子树和右子树时,则直接打印结点,完成终止。由此可见,树的中序遍历完全满足递归的两个条件,因此可以通过递归实现。例如下面这棵树:
当采用递归实现中序遍历时,程序执行的逻辑架构如下图所示:
其中,每个蓝色的括号都是一次递归调用。代码如下所示:
// 中序遍历
public static