数据结构还是算法思维的最终目的都是降低时间复杂度。
数据结构是从数据组织形式的角度达成这个目标,而算法思维则是从数据处理的思路上去达成这个目标。
递归
递归 (Recursion))是指在函数的定义中使用函数自身的方法(函数自己调用自己)
递归的两层含义:
- 递归问题必须可以分解为若干个规模较小、与原问题形式相同的子问题。并且这些子问题可以用完全相同的解题思路来解决;
- 递归问题的演化过程是一个对原问题从大到小进行拆解的过程,并且会有一个明确的终点(临界点)。一旦原问题到达了这个临界点,就不用再往更小的问题上拆解了。最后,从这个临界点开始,把小问题的答案按照原路返回,原问题便得以解决。
递归的基本思想就是把规模大的问题转化为规模小的相同的子问题来解决
递归的实现包含了两个部分,一个是递归主体,另一个是终止条件(必须有明确的结束条件,否则就会无限递归)
递归的算法思想
递归的数学模型就是数学归纳法
常见的题目:证明当 n 等于任意一个自然数时某命题成立。
证明步骤:
- 证明当 n = 1 时命题成立
- 假设 n = m 时命题成立,那么尝试推导出在 n = m + 1 时命题也成立
递归算法解决问题的步骤:
- 当你面对一个大规模问题时,如何把它分解为几个小规模的同样的问题
- 当你把问题通过多轮分解后,最终的结果,也就是终止条件如何定义
当一个问题同时满足以下 2 个条件时,就可以使用递归的方法求解:
- 可以拆解为除了数据规模以外,求解思路完全相同的子问题
- 存在终止条件
递归的案例
又见汉诺塔问题:
有三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着 64 片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上,并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
把这个问题抽象为一个数学问题,从左到右有 x、y、z 三根柱子,其中 x 柱子上面有从小叠到大的 n 个圆盘。现要求将 x 柱子上的圆盘移到 z 柱子上去。要求是,每次只能移动一个盘子,且大盘子不能被放在小盘子上面。求移动的步骤。
分析:
拆分成3个小问题
1.把从小到大的n-1个盘子从x移到y
2.把最大的一盘子从x移到z
3.再把从小到大的n-1个盘子从y移到z
随着递归体不断缩小范围,汉诺塔问题由原来“移动从小到大的 n 个盘子”,缩小为“移动从小到大的 n-1 个盘子”,直到缩小为“移动从小到大的 1 个盘子”。移动从小到大的 1 个盘子,就是移动最小的那个盘子。根据规则可以发现,最小的盘子是可以自由移动的。因此,递归的第二个条件,终止条件,也是满足的。
知道递归体包含 3 个步骤:
- 把从小到大的 n-1 个盘子从 x 移动到 y,那么代码就是 hanio(n-1, x, z, y);
- 再把最大的一个盘子从 x 移动到 z,那么直接完成一次移动的动作就可以了;
- 再把从小到大的 n-1 个盘子从 y 移动到 z,那么代码就是 hanio(n-1, y, x, z)。对于终止条件则需要判断 n 的大小。如果 n 等于 1,那么同样直接移动就可以了
public static void main(String[] args) {
String x = "x";
String y = "y";
String z = "z";
hanio(3, x, y, z);
}
public void hanio(int n, String x, String y, String z) {
if (n < 1) {
System.out.println("汉诺塔的层数不能小于1");
} else if (n == 1) {
System.out.println("移动: " + x + " -> " + z);
return;
} else {
hanio(n - 1, x, z, y);
System.out.println("移动: " + x + " -> " + z);
hanio(n - 1, y, x, z);
}
}