Java数据结构与算法——递归思维

一、递归的思维方式

递归思维方式的精髓在于判断能否将目前复杂的问题转换为较为简单的同类问题。可以的话,就先转换为简单的同类问题来解决,然后再利用同样的方法来解决复杂的同类问题。

注意:

不要陷入程序递归的内部去思考递归算法,要从递归思维的本质(复杂问题简单化)出发去理解递归算法。千万不要去通过试图解析程序执行的每一个步骤来理解递归(解析程序的执行是指给函数一个真实值,然后自己一步步去推出结果,这样的思考方式是错误的!),那样只会让自己得到伪理解的结果。记住!递归并不是算法,是一种复杂问题简单化的思维方式,而这种思维方式在程序中的体现就递归算法!递归算法在实现上就是函数不断调用自身的过程!

二、递归的定义

递归是数学中的一个重要概念,而递归算法则是针对程序设计而言的,两者在本质上是一样的。

递归的定义(从数学的角度)——用一个概念的本身直接定义自己。如阶乘 F(n) = n! 可以定义为:
n ! = { 1 , n = 0 , 1 n × ( n − 1 ) ! , n ≥ 2. n!=\left\{ \begin{aligned} 1 & , & n = 0,1 \\ n \times (n - 1) ! & , &n \geq 2. \end{aligned} \right. n!={1n×(n1)!,,n=0,1n2.
递归算法的定义(从程序的角度)——方法中调用自身的过程就称为递归算法。

递归必须满足以下两个条件:

(1)边界条件:至少有一条初始定义是非递归的,如汉诺塔的H(0)=0,阶乘的0!=1。
(2)递归通式:由已知函数值逐步计算出未知函数值,如汉诺塔的H(0)=0,可以推算出H(1)=H(0)+1+H(0)。

边界条件和递推通式是递归定义的两个基本要素,缺一不可,并且递归通式必须在有限次数内运算完成达到边界条件以保证能够正常结束递归,得到运算结果。

三、递归的三大要素

1、第一要素——明确你这个方法要干什么

对于递归,定义一个方法时,首先要清楚这个方法的功能是什么,而这完全由你自己来定义。也就是说,先不管方法里面的代码是什么,先弄明白这个方法是用来干什么就可以了。

例如,定义一个方法算阶乘:

// 算 n 的阶乘(假设n不为0)
int f(int n){
    
}

该方法的功能是算 n 的阶乘,接下来看第二要素。

2、第二要素——寻找递归结束条件

递归就是在方法内部调用这个方法本身,所以,我们必须要找出递归的结束条件,不然会一直调用自己,进入死循环。也就是说,我们需要找出当参数为多少时,递归结束,之后直接把结果返回(注意:这个时候我们必须能根据这个参数,直接知道方法的结果是什么)。

例如,n 的阶乘,当 n = 1 时,就能直接知道 f(1) = 1,所以 f(1) = 1 就可以作为上面方法的结束条件。把第二要素加进代码里面,如下:

// 算 n 的阶乘(假设n不为0)
int f(int n){
    if(n == 1){
        return 1;
    }
}

当然,当 n = 2 时,我们也可以直接知道 f(2) = 2,也就可以把 f(2) = 2 作为递归的结束条件。所以说,只要你觉得参数是什么时,你能够直接知道方法的结果,就可以把这个参数作为结束的条件。下面这样定义也是可以的。

// 算 n 的阶乘(假设n>=2)
int f(int n){
    if(n == 2){
        return 2;
    }
}

但是写成 n == 2 会漏掉 n = 1 的情况,为了更加严谨,可以写成:

// 算 n 的阶乘(假设n不为0)
int f(int n){
    if(n <= 2){
        return n;
    }
}

注意:

在寻找结束条件时,要多代入和检查,保证严谨,避免出现死循环。

3、第三要素——找出函数的等价关系式(递推公式)

第三要素就是,不断缩小参数的范围,但要保持原函数的结果不变。

例如阶乘,f(n) 这个范围比较大,可以让 f(n) = n * f(n-1)。这样,范围就由 n 变成了 n-1 。也就是说,要找到原函数的一个等价关系式,即数学中的递推公式。把这个等价关系式写进方法里,就得到了完整的算阶乘的方法。如下:

// 算 n 的阶乘(假设n不为0)
int f(int n){
    if(n <= 2){
        return n;
    }
    // 把 f(n) 的等价操作写进去
    return f(n-1) * n;
}

四、案例

1、斐波那契数列
(1)函数功能

假设 f(n) 的功能是求斐波那契数列第 n 项的值,代码如下:

int f(int n){
    
}
(2)递归结束条件

当 n = 1 或者 n = 2 时,f(1)= f(2)= 1。所以递归结束条件可以为 n <= 2 时,f(n)= 1。代码如下:

int f(int n){
    if(n <= 2){
        return 1;
    }
}
(3)函数的等价关系式

斐波那契数列满足递推公式: f(n) = f(n-1) + f(n-2)。最终代码如下:

int f(int n){
    // 1.先写递归结束条件
    if(n <= 2){
        return 1;
    }
    // 2.接着写等价关系式
    return f(n - 1) + f(n - 2);
}
2、小青蛙跳台阶

一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法?

(1)函数功能

假设 f(n) 的功能是求青蛙跳上一个 n 级台阶总共有多少种跳法,代码如下:

int f(int n){
    
}
(2)递归结束条件

当 n = 1 时,f(1) = 1。代码如下:

int f(int n){
    if(n == 1){
        return 1;
    }
}
(3)函数的等价关系式

每次跳的时候,小青蛙可以跳一个台阶,也可以跳两个台阶,也就是说,每次跳的时候,小青蛙有两种跳法。第一种跳法:第一次跳了一个台阶,剩下的 n-1 个台阶的跳法有 f(n-1) 种。第二种跳法:第一次跳了两个台阶,剩下的 n-2 个台阶的跳法有 f(n-2) 种。所以,小青蛙的全部跳法就是这两种跳法之和,即 f(n) = f(n-1) + f(n-2)。代码如下:

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

检查递归结束条件,当 n = 2 时,有 f(2) = f(1) + f(0)。我们知道,f(0) = 0,本应该递归结束,但上面的代码逻辑中,还会继续调用 f(0) = f(-1) + f(-2),导致无限调用,进入死循环。

所以,当我们在第二步找到一个递归结束条件时,先把结束条件写进代码,然后进行第三步。在找出等价关系式之后,要根据等价关系式验证结束条件是否漏掉了一些条件,然后完善结束条件,使得代码更加严谨。完整代码如下:

int f(int n){
    //f(0) = 0,f(1) = 1,f(2) = 2等价于 n<=2时,f(n) = n。
    if(n <= 2){
        return n;
    }
    ruturn f(n-1) + f(n-2);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 什么是二叉树? 二叉树是一种树形结构,其中每个节点最多有两个子节点。一个节点的左子节点比该节点小,右子节点比该节点大。二叉树通常用于搜索和排序。 2. 二叉树的遍历方法有哪些? 二叉树的遍历方法包括前序遍历、中序遍历和后序遍历。前序遍历是从根节点开始遍历,先访问根节点,再访问左子树,最后访问右子树。中序遍历是从根节点开始遍历,先访问左子树,再访问根节点,最后访问右子树。后序遍历是从根节点开始遍历,先访问左子树,再访问右子树,最后访问根节点。 3. 二叉树的查找方法有哪些? 二叉树的查找方法包括递归查找和非递归查找。递归查找是从根节点开始查找,如果当前节点的值等于要查找的值,则返回当前节点。如果要查找的值比当前节点小,则继续在左子树中查找;如果要查找的值比当前节点大,则继续在右子树中查找。非递归查找可以使用栈或队列实现,从根节点开始,每次将当前节点的左右子节点入栈/队列,直到找到要查找的值或者栈/队列为空。 4. 二叉树的插入与删除操作如何实现? 二叉树的插入操作是将要插入的节点与当前节点的值进行比较,如果小于当前节点的值,则继续在左子树中插入;如果大于当前节点的值,则继续在右子树中插入。当找到一个空节点时,就将要插入的节点作为该空节点的子节点。删除操作需要分为三种情况:删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。删除叶子节点很简单,只需要将其父节点的对应子节点置为空即可。删除只有一个子节点的节点,需要将其子节点替换为该节点的位置。删除有两个子节点的节点,则可以找到该节点的后继节点(即右子树中最小的节点),将其替换为该节点,然后删除后继节点。 5. 什么是平衡二叉树? 平衡二叉树是一种特殊的二叉树,它保证左右子树的高度差不超过1。这种平衡可以确保二叉树的查找、插入和删除操作的时间复杂度都是O(logn)。常见的平衡二叉树包括红黑树和AVL树。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值