数据结构与算法|第七章:递归

文章目录

  • 数据结构与算法|第七章:递归
    • 前言
    • 1.项目环境
    • 2.什么是递归
    • 3.递归公式
    • 4.递归的条件
    • 5.N阶楼梯问题
    • 6.递归的改写
      • 6.1 电影院例子改写
      • 6.2 N阶楼梯改写
    • 7.小结
    • 8.参考

数据结构与算法|第七章:递归

前言

递归是一种编程技巧,在许多数据结构和算法中都用了递归进行实现,如果要学习后面相对比较复杂的数据结构与算法,掌握递归非常重要。

1.项目环境

  • jdk 1.8
  • github 地址:https://github.com/huajiexiewenfeng/data-structure-algorithm
    • 本章模块:chapter06

2.什么是递归

我们先来看一个例子,比如我们在电影院看电影,想知道自己坐在第几排,但是电影院太黑没法自己数,于是我们问前面的人他在第几排,但是前面的人也不知道,所以他也问前面的人,依次类推,直到问到第一排的人,然后再一排一排的把数字传回来,每次加 1,最后你就知道具体在哪一排了。

这就是一个非常标准的递归求解问题的分解过程,去问的过程叫做 “递”,回答的过程叫做 “归”。

3.递归公式

大部分递归问题都可以用递归公式进行表示,以上面电影院的例子来说。递归公式如下:

f ( n ) = f ( n − 1 ) + 1 f(n) = f(n-1)+1 f(n)=f(n1)+1 其中, f ( 1 ) = 1 f(1) = 1 f(1)=1

  • f ( n ) f(n) f(n) 表示我们想知道自己在哪一排
  • f ( n − 1 ) f(n-1) f(n1) 表示前面一排所在的排数
  • f ( 1 ) = 1 f(1) = 1 f(1)=1 表示第一排的人知道自己在第一排

有了上面的公式,我们就可以写出递归的代码

    private static int f(int n) {
        if (n == 1) {
            return 1;
        }
        return f(n - 1) + 1;
    }

4.递归的条件

那究竟什么样的问题可以用递归来解决呢?只要同时满足以下三个条件,就可以用递归来解决。

1.一个问题的解可以分解为几个子问题的解

何为子问题?子问题就是数据规模更小的问题。比如,前面讲的电影院的例子,你要知道,“自己在哪一排”的问题,可以分解为“前一排的人在哪一排”这样一个子问题。

2.这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样

比如电影院那个例子,你求解“自己在哪一排”的思路,和前面一排人求解“自己在哪一排”的思路,是一模一样的。

3.存在递归终止条件

把问题分解为子问题,把子问题再分解为子子问题,一层一层分解下去,不能存在无限循环,这就需要有终止条件。

还是电影院的例子,第一排的人不需要再继续询问任何人,就知道自己在哪一排,也就是 f ( 1 ) = 1 f(1)=1 f(1)=1,这就是递归的终止条件。

5.N阶楼梯问题

有了上面的理论,我们再分析一个递归的例子,假设有 n 个台阶,每次可以跨 1 个台阶或者 2 个台阶,走完这个 n 个台阶有多少种走法?

我们先分析最简单的

假设 n = 1,那么我们只有一种走法,走 1 个台阶。

假设 n = 2,那么我们只有两种走法,走两次 1 个台阶或者一次走 2 个台阶。

假设 n = 3,我们现在已经走了第一步了,如果第一步走了 1 个台阶,那么我们需要考虑剩下的 2 个台阶的走法,其实就是 n=2 的走法,如果第一步走了 2 个台阶,那么我们只能走一步,也就是 n=1的走法,所以可以得到公式: f ( 3 ) = f ( 2 ) + f ( 1 ) f(3) =f(2)+f(1) f(3)=f(2)+f(1)

假设现在有 n 阶台阶,我们的走法,就是 n-1 阶的走法加上 n-2 阶的走法

用递归公式表示

f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n)=f(n-1)+f(n-2) f(n)=f(n1)+f(n2)

有了递归公式,接下来我们需要找到递归终止的条件

终止条件有两个

  • 最后一个台阶, f ( 1 ) = 1 f(1)=1 f(1)=1
  • 最后两个台阶,我们有两种走法, f ( 2 ) = 2 f(2)=2 f(2)=2

最终的代码如下:

    private static int f(int n) {
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 2;
        }
        return f(n - 1) + f(n - 2);
    }

6.递归的改写

我们是否可以将递归代码改写成非递归代价呢?

6.1 电影院例子改写

    private static int f2(int n) {
        int res = 0;
        for (int i = 0; i < n; i++) {
            res++;
        }
        return res;
    }

6.2 N阶楼梯改写

    private static int f2(int n) {
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 2;
        }
        int res = 0;
        int n_minus_1 = 2;// 假设 n=3,n-1=2 个台阶有2种走法
        int n_minus_2 = 1;// 假设 n=3,n-2=1 个台阶有1种走法
        for (int i = 3; i <= n; i++) {
            res = n_minus_1 + n_minus_2;
            n_minus_2 = n_minus_1;// 假设 n=4,第二次循环,4-2 = 2 有2种走法
            n_minus_1 = res;// 假设 n=4,第二次循环,4-1=3 有 3(上一次的2+1) 种走法
        }
        return res;
    }

通过上面两个例子可以看出,非递归的写法,不仅多出很多代码,而且非常难以理解,但是本质上和递归是一样的,只是"手动"进行递归。

7.小结

递归有利有弊,利是递归代码的表达力很强,写起来非常简洁;而弊就是空间复杂度高、有堆栈溢出的风险、存在重复计算、过多的函数调用会耗时较多等问题。

8.参考

  • 《数据结构与算法之美》王争

你可能感兴趣的:(数据结构与算法,数据结构,算法)