蓝桥杯学习——递归与循环

递归与循环的区别

递归,说白了就是自己调用自己,利用的是栈后进先出的原理。理论上,任何的循环都可以重写为递归形式,所有的递归也可以被表述成循环的形式

循环改递归的两大要点

  • 发现逻辑循环中的“相似性”
  • 找到递归的**“出口”**
  • 确定递归的参数

踢皮球思想理解递归

“踢皮球”-常用来形容政府职能部门职责不清,相互推诿,比如你要到部门A盖个章,A叫你先去部门B盖章,B叫你去部门C盖章…这种将工作以踢皮球的方式甩给其他人做的方式其实很适用于递归。但在递归中,如果你甩给其他人的工作和分配给你的工作完全一样,则程序陷入死循环,一直没有人进行工作,一直在甩锅,因此,递归中为:**自己做一小部分事情,剩下的事情再甩给其他人去做。**即上级做玩自己的工作然后交给下级去做。

递归调用

  • 递归调用仅仅是被调用函数恰为主调函数(自己调用自己)
  • 注意每次调用的层次不同
  • 注意每次分配形参并非同一个变量
  • 注意返回的次序

练习

试题 基础练习 01字串

问题描述

对于长度为5位的一个01串,每一位都可能是0或1,一共有32种可能。它们的前几个是:

00000

00001

00010

00011

00100

请按从小到大的顺序输出这32种01串。

输入格式

本试题没有输入。

输出格式

输出32行,按从小到大的顺序每行一个长度为5的01串。

样例输出

00000
00001
00010
00011
<以下部分省略>

循环解决

思路:暴力枚举

public class Main {    
	public static void main(String[] args) {
		for(int i=0;i<=1;i++){
			for(int j=0;j<=1;j++){
				for(int k=0;k<=1;k++){
					for(int l=0;l<=1;l++){
						for(int m=0;m<=1;m++){
							System.out.print(i);
							System.out.print(j);
							System.out.print(k);
							System.out.print(l);
							System.out.println(m);//注意前面的都是print,最后一个用println							
						}
					}
				}
			}
		}
	}
}

递归解决

思路:初始化目标字符串为空,长度为0,每次递归执行增加一个字符0和1两种情况,当字符长度达到5时结束递归

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
public class Main {
	static ArrayList<String>conArrayList=new ArrayList<>();
	public static void f(String s,int k) {
		if(k==5) {
		conArrayList.add(s);
		}
		else {
			f(s+'0', k+1);
			f(s+'1', k+1);
		}
		
	}
	public static void main(String[] args) {
		f("",0);
		for(String s:conArrayList) {
			System.out.println(s);
		}
		
	}

}

 

学习心得

时间和空间消耗

递归由于是函数调用自身,而函数调用时是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址及临时变量,而且往栈里压入数据和弹出数据都需要时间。这就不难理解有些时候递归实现的效率不如循环。所以相对于循环,递归空间和时间一般占用量比较大。但是相对于循环,递归更加简洁明了。

重复的计算

递归有可能很多计算都是重复的,从而对性能带来很大的负面影响。递归实际上是把一个大问题拆成很多小问题,如果那些小问题存在相互重叠的部分,就存在重复的计算。可以采用保存吗,每次递归计算结果的方式进行优化。

调用栈溢出

除效率之外,递归还有可能引起调用栈溢出。前面提到了需要为每一次函数调用在内存栈中分配空间,而每个进程的栈的容量是有限的。当递归调用的层级太多时,就会超出栈的容量,从而导致调用栈溢出。