08. 算法之递归算法

这篇具有很好参考价值的文章主要介绍了08. 算法之递归算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

递归,字面意思是递出去,拿回来,通过不断递过去,拿回来的过程,将每次调用结果保存起来,最后实现循环调用。递归在某些情况下会极大降低我们编程的复杂度。是软件开发工程师一定要掌握的技能。

1. 概念

递归:在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。也就是说,递归算法是一种直接或者间接调用自身函数或者方法的算法。
08. 算法之递归算法

2. 本质

递归,去的过程叫"递",回来的过程叫”归“。递是调用,归是结束后回来。是一种循环,而且在循环中执行的就是调用自己。递归调用将每次返回的结果存在栈帧中

2.1 递归三要素

  1. 递归结束条件
  2. 函数的功能,这个函数要干什么
  3. 函数等价公式,递归公式,一般是每次执行之间,或者个数之间逻辑关系

3. 小例子

3.1 循环实现

@Test
public void test1(){
    for (int i = 0; i < 5; i++) {
        System.out.println("你好呀");
    }
}

3.2 递归实现

void print(int count,String msg){
    //结束条件
    if (count<=0){
        return;
    }
    //函数功能
    System.out.println(msg);
    //等价公式
    print(--count,msg);
}

@Test
public void test2(){
    print(5,"你好呀");
}

4. 经典案例

4.1 斐波那契数列

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

4.2 规律:

从第3个数开始,每个数等于前面两个数的和

4.3 递归分析

  1. 函数功能:返回n的前两个数之和
  2. 结束条件:从第三个数开始,n<=2
  3. 等价公式:fun(n)=fun(n-1)+fun(n-2)

4.4 代码实现

@Test
public void test3(){
    int febnum = febnum(10);
    System.out.println(febnum);
}

int febnum(int n){
    if (n<0){
        System.out.println("输入错误");
    }
    //结束条件
    if (n==0 || n==1){
        return n;
    }
    //等价公式 函数功能
    return febnum(n-1)+febnum(n-2);
}

5. 时间复杂度

斐波那契数列 普通递归解法为O(2^n)

6. 优缺点

6.1 优点:

代码简单

6.2 缺点:

占用空间较大,如果递归太深,可能会发生栈溢出,可能会有重复计算通过备忘录或递归的方式去优化(动态规划)

7. 应用

递归作为基础算法,应用非常广泛,比如在二分查找、快速排序、归并排序、树的遍历上都有使用递归回溯算法、分治算法、动态规划中也大量使用递归算法实现

8. 斐波那契优化

通过上面的算法可以看到,我们实际做了大量的重复计算,比如计算f(5)需要计算f(4) 和 f(3),但是在计算f(4)的时候,又需要计算f(3)和f(2),又因为这是层层递归的,数字一大,递归的层数,计算的次数就会迅速扩张。为此,我们可以优化一下处理逻辑

8.1 动态规划

8.1.1 逻辑分析

斐波那契数的边界条件是 F(0) = 0 和 F(1) = 1当 >1 时,每一项的和都等于前两项的和,因此有如下递推关系:
F(n)= F(n -1) + F(n - 2)
由于斐波那契数存在递推关系,因此可以使用动态规划求解。动态规划的状态转移方程即为上述递推关系边界条件为 F(0) 和 F(1)。根据状态转移方程和边界条件,可以得到时间复杂度和空间复杂度都是 O(n)的实现。
由于 F(n)只和F(n - 1)与 F( - 2)有关,因此可以使用滚动组思想] 把空间复杂度优化成 0(1)

8.1.2 代码验证

public int fib(int n) {
	//因为数会很大,为此对结果取模,保证最终计算结果不会太大
    static final int MOD = 1000000007;

    if (n < 2) {
        return n;
    }
    int p = 0, q = 0, r = 1;
    for (int i = 2; i <= n; ++i) {
        p = q;
        q = r;
        r = (p + q)%MOD;
    }
    return r;
}

8.1.3 复杂度分析

  1. 时间复杂度为O(n)
  2. 空间复杂度为O(1)

8.2 矩阵快速幂

8.2.1 前置知识

  1. 如需求数据 a 的幂次,此处 a 可以为数也可以为矩阵,常规做法需要对a进行不断的乘积即 a * a * a * … 此处的时间复杂度将为 O(n)
  2. 以3^10为例
3^10=3*3*3*3*3*3*3*3*3*3

    =9^5 = 9^4*9

    =81^2*9

    =6561*9
  1. 基于以上原理,我们在计算一个数的多次幂时,可以先判断其幂次的奇偶性,然后:
  2. 如果幂次为偶直接 base(底数) 作平方,power(幂次) 除以2
  3. 如果幂次为奇则底数平方,幂次整除于2然后再多乘一次底数
  4. 对于以上涉及到 [判断奇偶性] 和 [除以2] 这样的操作。使用系统的位运算比普通运算的效率是高的,因此可以进一步优化:把 power % 2 == 1 变为 (power & 1) == 1。把 power = power / 2 变为 power = power >> 1

8.2.2 逻辑分析

08. 算法之递归算法

8.2.3 代码验证

package org.wanlong.recursion;

class Solution {
    public int fib(int n) {
        //矩阵快速幂
        if (n < 2) {
            return n;
        }
        //定义乘积底数
        int[][] base = {{1, 1}, {1, 0}};
        //定义幂次
        int power = n - 1;
        int[][] ans = calc(base, power);
        //按照公式,返回的是两行一列矩阵的第一个数
        return ans[0][0];
    }

    //定义函数,求底数为 base 幂次为 power 的结果
    public int[][] calc(int[][] base, int power) {
        //定义变量,存储计算结果,此次定义为单位阵
        int[][] res = {{1, 0}, {0, 1}};

        //可以一直对幂次进行整除
        while (power > 0) {
            //1.若为奇数,需多乘一次 base
            //2.若power除到1,乘积后得到res
            //此处使用位运算在于效率高
            if ((power & 1) == 1) {
                res = mul(res, base);
            }
            //不管幂次是奇还是偶,整除的结果是一样的如 5/2 和 4/2
            //此处使用位运算在于效率高
            power = power >> 1;
            base = mul(base, base);
        }
        return res;
    }

    //定义函数,求二维矩阵:两矩阵 a, b 的乘积
    public int[][] mul(int[][] a, int[][] b) {
        int[][] c = new int[2][2];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                //矩阵乘积对应关系
                c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
            }
        }
        return c;
    }
}

8.2.4 复杂度分析

  1. 时间复杂度 O(logn)
  2. 空间复杂度O(1)

8.3 备忘录

8.3.1 逻辑分析

前面提到计算复杂度高很大原因是重复计算,为此,很容易想到的是,我们将计算结果保存起来,下次先看看有没有计算过,如果计算过,就不重复计算了

8.3.2 代码验证

package org.wanlong.recursion;

import java.util.HashMap;
import java.util.Map;

/**
 * @author wanlong
 * @version 1.0
 * @description:
 * @date 2023/5/30 11:02
 */
public class SolutionWithMap {

    Map<Integer, Integer> result = new HashMap<>();

    int febnum(int n) {
        if (n < 0) {
            System.out.println("输入错误");
        }
        //结束条件
        if (n == 0 || n == 1) {
            return n;
        }
        //判断是否计算过
        if (result.get(n) != null) {
            return result.get(n);
        }
        int febnum1 = febnum(n - 1);
        int febnum2 = febnum(n - 2);
        result.put(n - 1, febnum1);
        result.put(n - 2, febnum2);
        //等价公式 函数功能
        int res = febnum1 + febnum2;
        result.put(n, res);
        return res;
    }


}

测试类验证

 //测试备忘录
 @Test
 public void test6(){
     int fib = new SolutionWithMap().febnum(10);
     System.out.println(fib);
 }

8.3.3 复杂度分析

  1. 时间复杂度O(n^2)
  2. 空间复杂度O(n)

以上,本人菜鸟一枚,如有错误,请不吝指正。文章来源地址https://www.toymoban.com/news/detail-464534.html

到了这里,关于08. 算法之递归算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 快速排序算法的递归和非递归

    基本思路 选择一个基准值,将数组划分三个区域,小于基准值的区域位于左侧,等于基准值的区域位于中间,大于基准值的区域位于右侧。将大于和小于区域继续进行分区,周而复始,不断进行分区和交换,直到排序完成 递归 思路: 步骤1: 在当前分区范围[l,r]中随机选中一

    2024年02月09日
    浏览(47)
  • 排序算法:归并排序(递归和非递归)

    朋友们、伙计们,我们又见面了,本期来给大家解读一下有关排序算法的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏: C语言:从入门到精通 数据结构专栏: 数据结构 个  人  主  页 : stackY、 ​ 目录 1.归并排序

    2024年02月07日
    浏览(37)
  • 递归以及斐波那契数列递归算法和迭代算法的实现与分析

    程序调用自身的编程技巧称为递归( recursion) 递归有两个过程,简单地说一个是 递的过程 ,一个是 归的过程 。 递归的两个必要条件 1. 存在限制条件 ,当满足这个限制条件的时候,递归便不再继续。 2.每次递归调用之后越来越 接近这个限制条件 . 递归本质就是函数调用

    2024年02月12日
    浏览(37)
  • 【算法专题】递归算法

    在解决⼀个规模为 n 的问题时,如果满足以下条件,我们可以使用递归来解决: 问题可以被划分为规模更小的子问题,并且这些子问题具有与原问题相同的解决⽅法。 当我们知道规模更小的子问题(规模为 n - 1)的解时,我们可以直接计算出规模为 n 的问题的解。 存在⼀种

    2024年02月03日
    浏览(38)
  • 程序员-你得把自己卖出去

    这篇文章跟朋友们聊些35岁发展的话题。都说35岁的中年人不如狗,网上又是铺天盖地的贩卖35岁中年焦虑各种割韭菜,再加上不稳定的外部环境,种种因素叠加确实会增加中年人的戾气和焦躁。 最近,我看到了一个关于罗振宇老师谈论35岁失业问题的视频,颇为有趣。罗老师

    2024年03月09日
    浏览(36)
  • 快速排序算法(递归非递归,三种方法实现,优化)

    快速排序 代码实现 ⚪单趟排序 版本一 ⚪快速排序 递归 关于快排优化 ⚪单趟排序 版本二 ⚪单趟排序 版本三 ⚪快速排序 非递归 特性总结 快速排序作为效率相对较高的排序,分别有递归与非递归两种写法,但都是 进行单趟排序,随后再解决其余问题。 快速排序的平均时间

    2024年02月05日
    浏览(42)
  • 八大排序算法之归并排序(递归实现+非递归实现)

    目录 一.归并排序的基本思想 归并排序算法思想(排升序为例) 二.两个有序子序列(同一个数组中)的归并(排升序) 两个有序序列归并操作代码: 三.归并排序的递归实现 递归归并排序的实现:(后序遍历递归) 递归函数抽象分析:  四.非递归归并排序的实现 1.非递归归并排序算法思想

    2024年02月03日
    浏览(39)
  • 常用算法——递推和递归算法

    一、递推算法: 递推算法是一种顺序递推的数学关系模型算法,好比通项公式。在数值计算的过程之中,只需要知道递推的边界值(一般是初值),也就是最开始的原始数值,然后类推,直到求出第n项数值,也就是目标值为终点。 二、递归算法: 递归算法:在计算机科学中是指

    2024年02月04日
    浏览(41)
  • 十大排序算法(中):冒泡排序,快速排序(递归和非递归)、归并排序(递归和非递归)

    这篇文章,我们接着来讲剩下的排序算法:冒泡排序,快速排序(递归和非递归)、归并排序(递归和非递归) 中心思想: 交换就是指根据序列中的两个元素的比较结果来对换这两个元素在序列中的位置,特点就是:将值较大的元素向序列尾部移动,将值较小的元素向序列

    2024年02月05日
    浏览(55)
  • 数据结构day08(树、算法)

    今日任务: 二叉树: 今日思维导图 链接: 快排:快速排序法(详解)_李小白~的博客-CSDN博客图画挺好啊 常见款:https://www.runoob.com/w3cnote/quick-sort.html  

    2024年02月10日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包