蓝桥杯 出栈顺序问题引发的思考以及递归的优化(缓存池)

关于递归的优化和思考

在我们IT圈内有句话,普通程序员用迭代,天才程序员用递归。诚然,递归确实能够将许多复杂的问题简化,但是问题来了,由于递归采用自顶向下的运算方式,未结果优化的递归往往做了大量的重复运算,这就给人产生了递归无用,递归耗时这一印象,但是不要忘了“天才程序员用递归”,如果善用递归,非但不会出现耗时的情况,反倒让代码简介易懂,高效,下面笔者选用一道蓝桥杯的真题出栈问题来揭开递归的神秘面纱!


在网上能找到大量的算法题(蓝桥杯、ACM等),由于这些算法题晦涩难懂,
很多文章直接给出代码,造成读者理解上的困难。
同时在网上也很难找到算法优化类的文章,利用这段特殊的时间,
笔者觉得有必要自己写一篇通俗易懂的文章,以下选用了两个较为经典的例子

递归会做大量重复运算 笔者自己画了一个递归树示意图

**

在此先引用著名的斐波那契数列(递归)

逐步引入下文的优化过程,必要知识准备

先来看下低效的普通代码
(递归实现斐波那契数列)
//说明 以下所有代码基于Java实现 为方便读者区分代码,类名使用中文名(不推荐使用中文名,易导致未知问题)

import java.util.Scanner;
public class 斐波那契数列_普通递归 {public static void main(String[] args) {System.out.println("输入斐波那契数列的项数n:");Scanner sc = new Scanner(System.in);int n = sc.nextInt();long start = System.currentTimeMillis();    //记录递归开始时间      时间单位:毫秒System.out.println(fibonacci(n));long end = System.currentTimeMillis();      //记录递归结束时间System.out.println("本次递归项数为:"+n+",耗时"+(end-start)/1000+"s");}public static long fibonacci(long number) {if ((number == 0) || (number == 1))return number;elsereturn fibonacci(number - 1) + fibonacci(number - 2);}
}


纳尼???求前50项数要49s,难不成太上老君炼丹也要七七四十九天???估计这段代码被老板看见当天就被炒鱿鱼了,递归的低效性被这以上代码体现的淋漓尽致,以上代码虽然简单,代价是太太太耗时了。


public class 斐波那契数列_递推 {public static double fib(int n){double f0=1;double f1=1;double i=2;double fn=0;for(i=2;i<n;i++){fn=f0+f1;f0=f1;f1=fn;}return fn;}public static void main(String[] args) {System.out.println("输入斐波那契数列的项数n:");Scanner sc = new Scanner(System.in);int n = sc.nextInt();long start = System.currentTimeMillis();    //记录递归开始时间      时间单位:毫秒System.out.printf("%.0f",fib(n));long end = System.currentTimeMillis();      //记录递归结束时间System.out.println("本次递归项数为:"+n+",耗时"+(end-start)/1000+"s");}
}

附上个普通的高效代码(递推)和运行截图,不重点讨论,相信笔者都能看懂,为防止越界,我将返回值类型设为double,尽情虐代码吧!!!

下方高能,拿好小板凳,笔和笔记本

  • [它来了,它来了,它终于来了,重头戏来了 ] 大名鼎鼎的递归缓存池来也!!!
  • 嗯哼,哎呀,说白了递归缓存池就是一个数组啦,也没啥,但要注意喽,数组一定要设置为全局变量哦,不然起不了作用滴@_@
  • 先上代码↓↓↓
 import java.util.Scanner;
public class 斐波那契数列_缓存池 {//全局变量位于任何方法之外,设置缓存池长度为500  确保足够用 可根据世纪需要修改static double[] pool = new double[500];     //缓存池public static void main(String[] args) {System.out.println("输入斐波那契数列的项数n:");Scanner sc = new Scanner(System.in);int n = sc.nextInt();long start = System.currentTimeMillis();    //记录递归开始时间      时间单位:毫秒System.out.printf("%.0f\n",fibonacci(n));long end = System.currentTimeMillis();      //记录递归结束时间System.out.println("本次递归项数为:"+n+",耗时"+(end-start)+"ms");    //此处改用ms作为单位  准确记录时间}public static double fibonacci(int number) {if ((number == 2) || (number == 1))return 1;if(pool[number]!=0)  //访问缓存池 判断缓存池中有没有记录当前运算的结果 有就直接返回结果return pool[number];//缓存池中找不到 将需要求解的运算值用一个变量存下来,再放到缓存池double x = fibonacci(number - 1) + fibonacci(number - 2);pool[number]=x;return x;}
}

先期待一波*_*
效果图来袭,准备好了吗??????

what happen???让我好好缓缓,说好的低效呢???神马???计算第50项用时6ms,刚才低效递归不是七七四十九秒吗???@_@,这运行效率提升了49s/7ms==??我拿计算器算算(疯狂按计算器中)。。。缓存池是个啥玩意???有那么神奇吗,是不是开挂???是不是图片PS过???确实PS过,用PS把以上5张图片合成一张图片,运行数据确确实实没问题。信不信???不信上机运行一下[笑哭]
解析:

来看看蓝桥杯真题的题目 ,笔者由浅入深逐一剖析;

 X星球特别讲究秩序,所有道路都是单行线。一个甲壳虫车队,共16辆车,按照编号先后发车,夹在其它车流中,缓缓前行。
路边有个死胡同,只能容一辆车通过,是临时的检查站,如图【p1.png】所示。X星球太死板,要求每辆路过的车必须进入检查站,也可能不检查就放行,也可能仔细检查。
如果车辆进入检查站和离开的次序可以任意交错。那么,该车队再次上路后,可能的次序有多少种?
为了方便起见,假设检查站可容纳任意数量的汽车。
显然,如果车队只有1辆车,可能次序1种;2辆车可能次序2种;3辆车可能次序5种。
现在足足有16辆车啊,亲!需要你计算出可能次序的数目。
这是一个整数,请通过浏览器提交答案,不要填写任何多余的内容(比如说明性文字)。


链接: 以下的代码优化基于此文章

先看一下低效代码

public class 出栈顺序_未优化 {// 不用管出站后车的数量和顺序,因为进站顺序和出站顺序已经决定出站时的排序static int f(int a, int b) {// a是等待进站的数目,b是站中的数目if (a == 0)// 此时已全部进站,出站时的顺序只有一种return 1;if (b == 0)// 此时车站为空,只能让车进站return f(a - 1, 1); //左侧候车区a辆车开一辆到车站 车站内就有一辆车// 有两种走法:1、让一辆车进站(候车区车减1 车站车加1) ;2、让一辆车出站(候车区的车不懂 车站内的车减1)return f(a - 1, b + 1) + f(a, b - 1);   //这两种走法形成所有的可能}public static void main(String[] args) {System.out.println(f(16,0));  //初始状态 车站内无车}
}
//答案是:35357670

本题数据量不大,只有16辆车,有35357670种可能性,幸好本题只有16辆车,如果有50辆车呢????好像有点难办!!!
上文提到的缓存池法只针对一个参数,对应一维数组,该递归函数有两个参数,可以设置一个二维数组作为缓存池。难点也在于多个参数可以设置一个多维数组作为缓存池

以下为优化代码(略作修改,可以自定义车的数量):

import java.util.Scanner;public class 出栈顺序 {static double[][] cache = new double[500][500]; //缓存池 根据世纪需要可调节缓存池大小static int n;public static void main(String[] args) {Scanner sc= new Scanner(System.in);System.out.println("输入指定的车辆数n:");n=sc.nextInt(); //指定输入的车数long start = System.currentTimeMillis();System.out.printf("%.0f\n",f(n,0));long end = System.currentTimeMillis();System.out.println("优化后的递归运行耗时"+(end-start)+"ms");}private static double f(int a, int b) {//a代表左边车道剩余的车 b代表栈中的车if(a==0)return 1;//左车道无车if(b==0)return f(a-1,1);  //栈中无车 需要来一辆if(cache[a][b]!=0)return cache[a][b];double x = f(a-1,b+1);cache[a-1][b+1]=x;double y = +f(a,b-1);cache[a][b-1]=y;return x+y;}
}

输入50辆车的测试用例:

最后来总结一下:
递归并非是一个低效算法,也并非非常难,只要先掌握最基本的递归,加上适当的优化方法,递归写出来的算法完全不输于其他算法。递归原本是一种以空间换时间的算法,花费极小的空间,代价是花费大量的时间,加入了缓存池后需要额外增加数组的空间开销,但大大节省了时间,递归的易用性加上优化后的高效性,足以写出不输任何方式的算法。希望读者阅读完本文后有所收获。最后提一下用缓存池法可能会遇到ArrayIndexOutOfBoundsException(数组越界异常),这个时候把数组的大小修改一下,不放心的话直接改为1k或1w即可解决问题。

蓝桥杯 出栈顺序问题引发的思考以及递归的优化(缓存池)相关推荐

  1. 蓝桥杯—出栈次序 (JAVA)

    题目描述: X星球特别讲究秩序,所有道路都是单行线.一个甲壳虫车队,共16辆车,按照编号先后发车,夹在其它车流中,缓缓前行. 路边有个死胡同,只能容一辆车通过,是临时的检查站,如上图所示. X星球太死 ...

  2. 出栈顺序问题讲解 蓝桥杯

    引言:最近刷数据结构的题,刷到一组元素入栈,他的出栈顺序有可能是哪些时卡住,之前没有关注此类问题,便写下总结 先通过几个例题讲解下出栈顺序问题 1. 一个栈的入栈序列是a,b,c,d,e则栈的不可能的 ...

  3. 判断出栈顺序的合法性(面试题)

    判断出栈顺序的合法性 "栈"是一种限制性线性表,是将线性表的插入.删除操作限制为仅在表的一端进行,一般将能够插入.删除的一端称为栈顶,表的另一端称为栈底.当栈中没有元素时称为空栈. ...

  4. (24) 不可能的出栈顺序

    一.问题描述 给定两个数组,一个进栈顺序,一个出栈顺序.判定出栈数组的出栈顺序是不是有可能的. 二.Code 1 package algorithm; 2 3 import java.util.Arr ...

  5. 出栈顺序 与 卡特兰数(Catalan)的关系

    一,问题描述 给定一个以字符串形式表示的入栈序列,请求出一共有多少种可能的出栈顺序?如何输出所有可能的出栈序列? 比如入栈序列为:1 2 3  ,则出栈序列一共有五种,分别如下:1 2 3.1 3 2 ...

  6. 数据结构----依据出栈顺序判断所需的最少栈空间

    1 问题描述 问题: 若元素 a,b,c,d,e,f,g 顺序进栈,且出栈顺序是 b,d,c,f,e,a,g 则栈的容量至少是_____ 答案:3 2 解法描述与分析 2.1 解法描述 记 1,2,3 ...

  7. 数据结构----出栈顺序有效性的判断

    1 问题描述 问题1:若元素 a,b,c,d,e,f 顺序进栈, 则不准许的出栈顺序是 A. d,c,e,b,f,a    B. c,b,d,a,e,f    C. b,c,a,e,f,d    D. ...

  8. 【PAT甲】1051 Pop Sequence (25分)判断出栈顺序的合法性

    problem 1051 Pop Sequence (25分) Given a stack which can keep M numbers at most. Push N numbers in th ...

  9. 判断栈的出栈顺序是否正确

    一 问题描述:      两个数组pPush和pPop分别存储了压栈序列和出栈序列,如何判断出栈序列是否正确,假设元素不重复.      需要实现的函数: bool isStackOutRight(i ...

最新文章

  1. linux下挂载移动硬盘
  2. zblog如何调用HTML,Zblog调用栏目文章的方法
  3. idea ssm打war包_IDEA下从零开始搭建SpringBoot工程
  4. 可在单片机上运行的简易图形库
  5. oracle 根据分隔符提取,oracle自定义函数按照某个分隔符拆分字符串
  6. lnmp 清除mysql日志,军哥LNMP 如何关闭 Mysql 日志,并且删除 mysql-bin.0000*日志文件...
  7. adprw指令通讯案例_超实用,非常典型的Modbus通讯项目案例,三分钟学会
  8. 爱奇艺网络协程编写高并发应用实践
  9. python实现给定一个列表判断里面是否有重复元素
  10. 打不开malloc和free函数
  11. 阿里云首席安全研究员吴翰清:我人生的两次选择
  12. 信度spss怎么做_毕业季:毕业论文利用spss做信度分析步骤详解
  13. C语言学习教程,用C语言编写扫雷游戏
  14. R语言中dim函数_R语言在医学统计中的应用基础教程
  15. 智能陈桥五笔输入法 for linux,解决在Linux下安装陈桥五笔输入法的方法
  16. 基于OpenCV双边滤波器的人脸美化 .
  17. innodb_flush_method 的理解
  18. 技术分享 | 排序(filesort)详细解析(8000 字长文)
  19. 【NOIP2014普及组】子矩阵
  20. 【java-日志组件】slf4j+logback配置及详解

热门文章

  1. VSAN报错:该主机的VSphere HA 检测信号数据存储数据为1,少于要求数目:2
  2. Oracle数据库笔记以及plsql的使用
  3. java 缩放图片 锯齿_Java Image Filters 图像缩放质量差的问题解决
  4. H5水果机,一个网络版的lao hu ji
  5. 带有 Utopia Messenger 的免费 ChatGPT 助手
  6. tkinter —— Tcl/Tk 的 Python 接口
  7. 深圳软件测试学习:软件测试这个职业能干多久?
  8. 免费又好用的屏幕录像/直播软件:Open Broadcaster Software
  9. PyQt5安装失败问题解决
  10. 浙江事业单位考不考计算机专业知识,浙江事业单位考试计算机相关综合知识,看哪些?...