[Dart] 递归的逆向思维

前言

学习工作中经常会遇到一些需要重复做或者说需要遍历的问题,通常我们都会使用“循环”来作为解决方法。for循环,while循环写的贼6,对付这些常见的遍历问题它似乎总是能对症下药。一个for不能解决问题,那我就再嵌套个for。不可否认,循环能解决大部分问题,但很多时候用一种其他的办法能更快更简便地解决问题,这个方法就是递归
我们写for循环时大部分时候是正向思维,体现在我们写for循环总是 i++,i 从0到n遍历,因为很多时候i++已经能解决问题了,如此长期以往,已经生成了固定正向思维。而,递归,则是要打破这种正向思维,它是从末到始,从未知到已知的遍历过程。

从数字求和开始

经典问题:求0-100所有的数字之和。
拿到问题,你马上写出一个for循环,这不是简简单单,叉着腰说,我的手已经触发肌肉记忆了,闭着眼都能写出来。

int getNum(int num) {
  int sum = 0;
  for (int i = 0; i <= num; i++) {
    sum += i;
  }
  return sum;
}

此时你的同事默默抛出一个递归方法,产品经理走过来说,5行 < 7行,同事赢!
你突然想到你的代码还能更精简,马上说“我还能改”,可惜一晃眼同事们都去吃饭了。
其实并不是说递归方法少两行就是比循环的方法好,两种都是可行的解决方法,但不认识递归的话,我们每次只能想到第一种方法,技多不压身,能学就学,下面解析一下数字求和的递归代码。

int getNum(int num) {
  if (num == 1) return 1;
  final pre = getNum(num - 1);
  return pre + num;
}

递归的结构

我们先拿出简单的数字求和递归方法,它虽然很简单,但却麻雀虽小五脏俱全,它就是递归函数的最基础的形态。

int fx(int n) {
  if (n == 1) return 1; // row 1
  final pre = fx(n - 1); // row2
  return pre + n; // row3
}

row1:
递归函数最主要的一点是递归函数一定要有一个出口,如果没有出口,那它就会变成一个无限递归,也就是我们说的死递归。死递归是很可怕的,这是我们要避免的问题。所以我们我们递归的第一行就是出口,如上面方法的row1。而递归的出口是很容易找到的,一般就是问题的已知部分。在数字之和的问题中,我们都知道0+1的数字之和为1,所以我们就可以直接判定,如果num等于1时返回1。
`
row2:
如果是循环,我们是从1开始,一直遍历到n。但递归不是,递归教我们想问题要从未知到已知。
比如我们要计算1-5的数字之和,1是已知,而2,3,4,5我们都是未知,想要知道5的总和,我们要是能知道4的总和就好了,4的总和加上5就是5的总和了啊,想要知道4的总和又要了解3的总和......,所以我们想要知道n的总和,就要知道它的前一个n-1总和,而row2就是代表当前n的上一个总和。

row3:
我们通过row2知道了当前n的上一个数n-1的总和,接下来只要用上一个的总和加上自己就可以直接返回了。如此循环,直到到达n==1,才走出出口。

这样我们就大概了解了递归的基本结构,由以下组成
1:出口
2:当前n的上一个数n-1的结果fx(n-1)
3:fx(n-1)fx(n)的条件

更多例子

计算数的阶乘
// 数的阶乘
int fx(int n) {
  if (n == 1) return 1;  // 出口
  final pre = fx(n - 1);  // 当前n的上一个数n-1的结果fx(n-1)
  return pre * n;  // fx(n-1)到fx(n)的条件
}
斐波那契数列

1、1、2、3、5、8、13、21、34、……

// 斐波那契数列
int fx(int n) {
  if (n == 1 || n == 2) return 1;
  final pre1 = fx(n - 1);
  final pre2 = fx(n - 2);
  return pre2 + pre1;
}
猴子吃桃问题

猴子吃桃问题:猴子第一天摘下若干个桃子,吃了一半,又多吃了一个。 第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天都吃了 剩下的一半零一个。到第10天早上想再吃只剩一个了。求第一天共摘了多少个?

int fx(int n) {
  if (n == 10) return 1;
  final next = fx(n + 1);
  return (next + 1) * 2;
}

总结:

总的来说,正如我在文中一直说的,递归是一种从未知到已知的遍历过程,在本文中只是浅浅谈了它的基本结构,我们要学习的不只是它的使用方法,还有它的逆向思维方式,从未知到已知,已知反馈给未知,我想这对于我们来说更重要。

posted @ 2022-11-06 19:33  漫游者杰特  阅读(121)  评论(0编辑  收藏  举报