数据结构与算法学习笔记之高效、简洁的编码技巧“递归”

前言

盗梦空间想象大多数人都看过:电影讲述的是主人公诺兰进入希里安·墨菲梦境植入想法的行动。为了向希里安·墨菲梦植入理念,影片进入四层梦境,即所谓:“梦中的梦中 梦中人的梦中”。

有一对兔子,每隔三个月会产下一对小兔子,小免子每隔三个月,也会产生新的一对免子,问36个月后,共有多少对兔子。

诸如此类:其实就是“递归”,今天就一起进入“递归”的世界看看

正文

一、递归的定义

1.递归是一种应用广泛的算法,既能运用到软件开发中成为高效、简洁的编码技巧也能应用到生活中解决实践递归问题,比如DFS深度优先搜索、前中后序二叉树遍历等,又比如计算不断繁衍的后台个数等等;

2.程序调用自身的方式称为递归调用,去调用的过程称为递,回来的过程称为归。

3.基本上,所有的递归问题都可以用递推公式来表示,比如

f(n) = f(n-1) + 1;  f(n) = f(n-1) + f(n-2); f(n)=n*f(n-1);

二、为什么使用递归?

1.递归在解决某些问题的时候使得我们思考的方式得以简化,代码也更加精炼,容易阅读 2.递归在处理问题时要反复调用函数,这增大了它的空间和时间开销,空间复杂度高、有堆栈溢出风险、存在重复计算、过多的函数调用会耗时较多等问题。

三、什么样的问题可以用递归解决呢?

一个问题只要同时满足以下3个条件,就可以用递归来解决: 1.问题的解可以分解为几个子问题的解。何为子问题?就是数据规模更小的问题。 2.问题与子问题,除了数据规模不同,求解思路完全一样 3.存在递归终止条件,不能无限循环。

四、如何实现递归

1.递归代码编写

写递归代码的关键就是将大问题分解为小问题,写出递推公式,找出终止条件,最后将递推公式和终止条件翻译成代码。

举一个栗子:

假如这里有n个台阶,每一次你可以跨过一或二个台阶,请问有几种走法?

根据第一步的走法把走法分为两类,第一步走一个台阶或者走两个台阶,所以n个台阶的走法就等于先走一阶的走法加上先走两个台阶的走法,递归公式为:

f(n) = f(n-1)+f(n-2)

当只有一个台阶时,我们就不需要递归了,所以终止条件为:

f(1)=1

但是只有它还不足够,n=2时,f(2)=f(1)+f(0)还有f(0)=1,也就是第0阶也要有一种走法,不和逻辑,所以终止条件还有一个:

f(2)=2

编写为代码为:

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

2.递归代码理解

对于递归代码,若试图想清楚整个递和归的过程,实际上是进入了一个思维误区。 那该如何理解递归代码呢?如果一个问题A可以分解为若干个子问题B、C、D,你可以假设子问题B、C、D已经解决。而且,你只需要思考问题A与子问题B、C、D两层之间的关系即可,不需要一层层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样子理解起来就简单多了。 因此,理解递归代码,就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。

五、递归常见问题及解决方案

1.警惕堆栈溢出:可以声明一个全局变量来控制递归的深度,从而避免堆栈溢出。

代码实现:

// 全局变量,表示递归的深度。
int depth = 0;

int f(int n) {
  ++depth;
  if (depth > 1000) throw exception;
  
  if (n == 1) return 1;
  return f(n-1) + 1;
}

2.警惕重复计算:通过某种数据结构来保存已经求解过的值,从而避免重复计算。

如用散列表来保存已存在的值,改写上面的代码如下:

public int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  
  // hasSolvedList 可以理解成一个 Map,key 是 n,value 是 f(n)
  if (hasSolvedList.containsKey(n)) {
    return hasSovledList.get(n);
  }
  
  int ret = f(n-1) + f(n-2);
  hasSovledList.put(n, ret);
  return ret;
}

(代码来源于网络)

六、如何将递归改写为非递归代码? 笼统的讲,所有的递归代码都可以改写为迭代循环的非递归写法。如何做?抽象出递推公式、初始值和边界条件,然后用迭代循环实现。

将上面的代码实现如下:

int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  
  int ret = 0;
  int pre = 2;
  int prepre = 1;
  for (int i = 3; i <= n; ++i) {
    ret = pre + prepre;
    prepre = pre;
    pre = ret;
  }
  return ret;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏落影的专栏

程序员进阶之算法练习(二十五)

前言 算法题是锻炼思维的好工具,在解决问题的层面考察思考能力。 正文 1. Compote 题目链接 题目大意: 给出a、b、c三种材料,可以1:2:4组成...

39990
来自专栏七夜安全博客

从多项式相加看线性结构

14230
来自专栏专注数据中心高性能网络技术研发

[LeetCode]HashTable主题系列{第3题}

1. 内容介绍 开一篇文章记录在leetcode中HashTable主题下面的题目和自己的思考以及优化过程,具体内容层次按照{题目,分析,初解,初解结果,优化解...

37490
来自专栏数据结构与算法

Debug

复杂度证明 普通莫队时间复杂度为 证明: 当我们第i个询问转移的第i+1个询问时 如果第i个询问区间和第i+1个询问区间的左端点所在块的编号相同,那么左端...

361110
来自专栏数据分析

[数据清洗]-看上去一样的数字

数据不正确(格式不正确,数据不准确,数据缺失)我们做什么都是徒劳。数据清洗时数据分析的第一步,也是最耗时的一步。 数据清洗很枯燥,但是随着数据清理技巧越来越熟练...

29730
来自专栏人工智能头条

Python 再牛,在字符串排序上还是被 Julia 和 R 碾压

在《实例对比 Julia, R, Python,谁是狼语言?》我们简单介绍了 Julia 的背景,以及通过优化一个似然函数的参数 μ 和 σ,来对比 Julia...

13930
来自专栏架构说

折半查找部分有序

部分有序 本周qq群有人咨询 Search in Rotated Sorted Array 来源: https://leetcode.com/proble...

29790
来自专栏进击的程序猿

进击算法:字符串匹配的 BM 算法

各种文本编辑器的 "查找" 功能(Ctrl+F),大多采用 Boyer-Moore 算法。

33530
来自专栏ACM小冰成长之路

51Nod-1645-中位数变换

ACM模版 描述 ? 题解 这个题很明显是找规律的问题,直接暴力肯定会超时……虽然我也是暴力也两发才反应过来……平时做题总是抱着侥幸心理,比赛时却总是胆小如鼠…...

23070
来自专栏HansBug's Lab

从Hash Killer I、II、III论字符串哈希

首先,Hash Killer I、II、III是BZOJ上面三道很经典的字符串哈希破解题。当时关于II,本人还琢磨了好久,但一直不明白为啥别人AC的代码都才0....

35840

扫码关注云+社区

领取腾讯云代金券