zoukankan      html  css  js  c++  java
  • 数据结构与算法学习笔记之高效、简洁的编码技巧“递归”

    前言

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

    有一对兔子,每隔三个月会产下一对小兔子,小免子每隔三个月,也会产生新的一对免子,问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编号的数组

     

    数据结构与算法学习笔记之后进先出的“桶”

     

    数据结构与算法学习笔记之先进先出的队列

    以上内容为个人的学习笔记,仅作为学习交流之用。

     

    欢迎大家关注公众号,不定时干货,只做有价值的输出

    作者:Dawnzhang 
    出处:https://www.cnblogs.com/clwydjgs/p/9810969.html

    版权:本文版权归作者
    转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

  • 相关阅读:
    兔子数
    忠诚
    mysql字段名与关键字冲突(near "to":syntax error)
    C/C++使用心得:enum与int的相互转换
    ubuntu重新安装 apache2
    ubuntu 删除mysql
    Notepad++ 代码格式化
    linux文件字符集转换(utf8-gb2312)
    字符编码详解——彻底理解掌握编码知识,“乱码”不复存在
    c语言判断是否是utf8字符串,计算字符个数
  • 原文地址:https://www.cnblogs.com/clwydjgs/p/9810969.html
Copyright © 2011-2022 走看看