递归——学习心得

通过学习基本可以了解到以下三点:

  • 将递归问题分解成递归步骤和基本案例
  • 知道何时使用递归
  • 了解递归与迭代的优缺点

下面将从这三点出发进行总结概括。

递归对于许多人来说相信并不陌生,我们以前看过或编写过相应递归函数。

eg:阶乘和斐波那契亦或者是汉诺塔问题

递归函数是根据基本情况和递归步骤定义的。

  • 在基本情况下,我们将给定函数调用的输入立即计算出结果。
  • 在递归步骤中,我们借助于对该函数的一个或多个递归调用来计算结果,但是输入以某种方式减小了大小或复杂度,更接近于基本情况。

递归不难理解就是循环调用该方法直至完成目的的意思。但是在循环过程中很可能会将自己绕晕。下面看一个小例子来看一下我们常用的for循环以及递归是如何实现的。
在这里插入图片描述
迭代式:

public static long factorial(int n) {
  long fact = 1;
  for (int i = 1; i <= n; i++) {
    fact = fact * i;
  }
  return fact;
}

递归式

public static long factorial(int n) {
  if (n == 0) {
    return 1;
  } else {
    return n * factorial(n-1);
  }
}

我们不难看出在递归式中需要设置一个终结条件,在此例中即n==0,当n!=0时,递归调用factorial方法直至n为0然后依次算出相应得数。以传入值为3来进行应用。

在该图中,我们可以看到堆栈如何随着main调用的增长而增长factorial,factorial然后调用自身,直到factorial(0)不进行递归调用为止。然后,调用堆栈展开,每个调用factorial将其应答factorial(3)返回给呼叫者,直到返回main。

递归实现的结构

递归实现始终包含两个部分:

  • 基本案例,这是问题的最简单,最小的实例,无法进一步分解。基本情况通常对应于空度–空字符串,空列表,空集,空树,零等。

  • 递归步骤,它将一个较大的问题实例分解为一个或多个可以通过递归调用解决的更简单或更小的实例,然后将这些子问题的结果重新组合以产生原始问题的解决方案。

我们将问题实例转换成较小的内容极为关键,否则的话问题就会困难亦或者递归进入死循环无法结束。

选择正确的递归子问题

当我们写出关键递归算法时,应考虑递归子问题是否可以以更细微的方式变得更小或更简单,我们应在纸上或画图板写下思路。

eg:将整数转换为具有给定基数的字符串表示形式

/**
 * @param n integer to convert to string
 * @param base base for the representation. Requires 2<=base<=10.
 * @return n represented as a string of digits in the specified base, with 
 *           a minus sign if n<0.
 */
public static String stringValue(int n, int base)

例如,stringValue(16, 10)应该返回"16",并且stringValue(16, 2)应该返回"10000"。

我们可以简单地通过递归调用相应正整数的表示来处理负整数:

if (n < 0) return "-" + stringValue(-n, base);

假设我们在底数10中有一个正数n,例如n = 829,我们应该如何将其分解为递归子问题?考虑要写在纸上的数字,我们可以以8(最左边或最高位的数字)或9(最右边的低位数字)开头。从左端开始看起来很自然,因为这是我们编写的方向,但是在这种情况下更难,因为我们需要首先找到数字中的位数以弄清楚如何提取最左边的位数。相反,更好的分解n的方法是取其余数取模底数(给出最右边的数字),然后除以底数(给出子问题,剩下的高阶数字):

return stringValue(n/base, base) + "0123456789".charAt(n%base);

考虑几种解决问题的方法,然后尝试编写递归步骤。 您想找到一个产生最简单,最自然的递归步骤的步骤。

仍然需要弄清楚基本情况是什么,并包括一个if语句,该语句将基本情况与此递归步骤区分开。

何时使用递归?

我们已经看到了使用递归的两个常见原因:

  • 问题是自然递归的(例如斐波那契)
  • 数据自然是递归的(例如文件系统)

使用递归的另一个原因是要更多地利用不变性。在理想的递归实现中,所有变量都是最终变量,所有数据都是不可变的,并且从某种意义上说,递归方法都是纯函数,因为它们什么都不会变异。方法的行为可以简单地理解为其参数与其返回值之间的关系,而对程序的任何其他部分均无副作用。这种范例称为函数式编程,比使用循环和变量的命令式编程要容易得多。

相比之下,在迭代实现中,您不可避免地具有非最终变量或可变对象,这些变量或变量在迭代过程中会被修改。然后,要对程序进行推理就需要考虑各个时间点上的程序状态快照,而不是考虑纯输入/输出行为。

递归的一个缺点是,它可能比迭代解决方案占用更多的空间。建立递归调用堆栈会暂时占用内存,并且堆栈的大小受到限制,这可能会限制递归实现可以解决的问题的大小。

总结

  • 递归代码更简单,并且经常使用不可变变量和不可变对象。
  • 对于自然递归问题,递归实现通常比迭代解决方案短,更容易理解。
  • 递归代码是自然可重入的,故它更安全,可以在更多情况下使用。(递归(一种称为自身的方法)是编程中一种普遍现象的特殊情况,称为重入)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值