算法笔记----递归算法

Posted 小龚同学233

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法笔记----递归算法相关的知识,希望对你有一定的参考价值。

文章目录

概念

递归算法的优缺点

经典例题

递归算法的优化

实战演练


概念

官方解释:一种通过重复将问题分解为同类的子问题而解决问题的方法。

我的理解:一种把大问题拆分成若干个算法逻辑相同的子问题,只有数据的大小发生改变。简单来说就是:“不停直接或间接调用自身函数,每次调用会改变一个或者多个变量,直到变量到达边界,结束调用。”

使用场景:

1.个大问题可以拆分为多个子问题的解。

2.拆分后的子问题和原问题除了数据大小不一样,解决思路完全相同。

3.存在递归的终止条件

注意:写递归代码一定要注意方法的语义(方法的功能),不能陷入递归内部。当做调用别人写好的代码。

递归算法的优缺点

优点:只需要几条代码就可以解决问题,特别适合以下三类问题:

        (1)数据的定义是按递归定义的。(Fibonacci函数)

        (2)问题解法按递归算法实现。(回溯) 

        (3)数据的结构形式是按递归定义的。(树的遍历,图的搜索) 

缺点:(1)有时因为太过简洁而难理解过程。

           (2)递归会保存大量临时数据和重复的数据,太多的话,会造成栈溢出,程序崩溃。

           (3)数据规模大时,运行时间可能会超时。

经典例题

1.、求解Fibonacci数列的第n个位置的值?(斐波纳契数列(Fibonacci Sequence),又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、在数学上,斐波纳契数列以如下被以递归的方法定义:F1=1,F2=1,Fn=F(n-1)+F(n-2(n>2,n∈N*))。

设计步骤:

 代码:

public class lanqiao 
    public static int Fibonacci(int n)
        // 1. 递归函数的终止条件
        if(n == 1 || n == 2)
            // 2.终止完要返回什么结果
            return 1;
        
        // 3.递归部分(它具有什么功能)
        return Fibonacci(n-1) + Fibonacci(n-2);
    
    public static void main(String[] args) 
        System.out.println(Fibonacci(10));
    

不过由于递归算法存在许多重复计算,导致计算效率非常低,一般计算第20位就得等30多秒。如果想检验自己电脑的计算能力,可以输入50试一试。

演示图:

 如图所示重复计算,势必增加运算时间和内存空间。

2、 汉诺塔问题是一个经典的递归问题。汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,

在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。假设我们需要这些盘片从A柱移动到C柱,应该如何操作?

设计步骤:

 

 

由上图可以不妨用数学归纳法找找规律:

                                                               1个圆盘的次数 2的1次方减1

                                                               2个圆盘的次数 2的2次方减1

                                                               3个圆盘的次数 2的3次方减1

                                                               .....

                                                               n个圆盘的次数 2的n次方减

  所以移动次数就是:2^n-1.

  在上面数学问题解法的分析下,我们不难发现,移到的步数必定为奇数步:

    (1)中间的一步是把最大的一个盘子由A移到C上去;

    (2)中间一步之上可以看成把A上n-1个盘子通过借助辅助塔(C塔)移到了B上,

    (3)中间一步之下可以看成把B上n-1个盘子通过借助辅助塔(A塔)移到了C上;

代码:

package com.company;
import java.util.Scanner;
import java.util.Arrays;


public class lanqiao 
   
    static int times;

    public static void main(String[] args) 
        char A = 'A';
        char B = 'B';
        char C = 'C';
        System.out.println("汉诺塔游戏开始啦");
        System.out.println("请输入盘子数:");
        Scanner stdin = new Scanner(System.in);
        int n = stdin.nextInt();
        //调用汉诺塔
        hannoi(n, A, B, C);
        stdin.close();

    
    
    public static void move(int disk, char M, char N) 
        System.out.println("第" + (++times) + "次移动, 盘子" + disk + "  " + M + "------->" + N);
    


    public static void hannoi(int n, char A, char B, char C) 
        if (n == 1) 
            move(n, A, C);
         else 
            //移动上一关的步骤移动到B
            hannoi(n - 1, A, C, B);
            //把最大的盘子移动C塔
            move(n, A, C);
            //再把B上的上一关的盘子移动到C上就可以了
            hannoi(n - 1, B, A, C);

        
    

小结:汉诺塔和斐波那契数列一样,通过数学的角度你会发现它俩数据的定义是都是符合递归定义的。

递归算法的优化

   所谓优化无非就是从减少时间和空间复杂度,我们在上面说了递归算法最大的缺点就是存在大量的重复运算,所以我们的优化就是为了解决重复运算问题。

  计算机没有人的智慧,不懂得拐弯,比如我们在学正态分布时,老师并不会让我们去直接去计算,而是会让我们直接去查标准正态分布表。同理我们是不是也可以帮计算机设计一个备忘录,把每一次计算的过程和结果记录下来,如果计算机又遇到相同的计算,它就可以之间从备忘录里取得自己需要的答案。

例如上面的斐波那契数列,我们可以创造一个备忘录(数组)来记录。

代码:

package com.company;
import java.util.Scanner;
import java.util.Arrays;


public class lanqiao 


        public static int helper(int []arr,int n)
        // base case 
            if(n==1||n==2)
             
                return 1;
            // 已经计算过
            if(arr[n]!=0)
                return arr[n];
            arr[n]=helper(arr,n-1)+helper(arr,n-2);
            return arr[n];
        
        public static int fib(int N)
        
            if(N<1)
                return 0;
// 备忘录全初始化为 0
            int []arr=new int [N+1];
 // 初始化最简情况
            return helper(arr,N);
        

        public static void main(String[] args) 
            System.out.println(fib(20));
        
    

带「备忘录」的递归算法,把一棵存在巨量冗余的递归树通过「剪枝」,改造成了一幅不存在冗余的递归图,极大减少了子问题(即递归图中节点)的个数。本算法的时间复杂度是 O(n),比起常规的递归基本就是降维打击了。

实战演练

问题描述:数的计数(蓝桥杯试题集ALGO-532
  我们要求找出具有下列性质数的个数(包含输入的自然数n):
  先输入一个自然数n(n<=1000),然后对此自然数按照如下方法进行处理:
  1. 不作任何处理;
  2. 在它的左边加上一个自然数,但该自然数不能超过原数的一半;
  3. 加上数后,继续按此规则进行处理,直到不能再加自然数为止.

       输出符合该条件的数字的数目。

例:输入6  输出6(6,16,26,36,126,136)

算法分析:

              1.首先我们通过题目条件中的第2点和第3点可以判断出它符合递归的使用范围。

             2.因为本题并没有让我们输出符合条件的数字,所以我们可以把问题简化为:分解一个数,分解出来的数不大于原来数的一半,如果分解出来的数大于1,则继续执行同样运算,等于1时递归结束。

代码:

#include <iostream>
using namespace std;
int sum = 1;
int digui(int n)

    if (n == 1)
   
        return 1;
   
    for (int i = 1; i <= n / 2; i++)
   
        sum++;
        if (i > 1)
       
            digui(i);
       
   
    return sum;

int main()

    int n;
    cin >> n;
   cout<< digui(n);
    return 0;
    

 

小结:递归算法的运用一定要理清思路,找到递归终止条件,搞明白递归函数到底实现了一个什么功能即可,切记不可陷入递归内部,(如果你是爱因斯坦级别的大佬)那就当我没说过。

  

以上是关于算法笔记----递归算法的主要内容,如果未能解决你的问题,请参考以下文章

尚硅谷算法与数据结构学习笔记05 -- 递归

树的存储结构的设计及递归遍历(前序,后序,层序)算法实现——Java数据结构与算法笔记

树的存储结构的设计及递归遍历(前序,后序,层序)算法实现——Java数据结构与算法笔记

树的存储结构的设计及递归遍历(前序,后序,层序)算法实现——Java数据结构与算法笔记

树的存储结构的设计及递归遍历(前序,后序,层序)算法实现——Java数据结构与算法笔记

算法笔记_017:递归执行顺序的探讨(Java)