递归算法

递归

程序调用自身的编程技巧称为递归(recursion)。递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

递归的主要思考方式在于:把大事化小

当我们面对大规模问题时,只要这个问题含有一定规律,并且可以循环设计,那么我们就不必正向暴力破解式的用循环解决。

如果使用迭代循环,虽然对于时间复杂度和空间复杂度上更有优势,但是需要开发者思考的更加全面和复杂,增加了写代码的负担。

迭代和递归的比较:
https://blog.csdn.net/qq_40893595/article/details/101717873

递归的根本思想

递归的主要思考方式在于:把大事化小

将大问题,逐渐简化为小问题传入下一层递归,直到最小,然后设置条件出口,这样就可以的到完整大问题的答案。

例如:

var foo = 'bar';
int fact(int n){
  if(n <= 1)
      return 1;
  else
      return n * fact(n - 1);
}

在这里插入图片描述

例题实践

串的翻转

思路:假设字符串为“abcde”,每次递归循环都要将第一个与剩下的部分分开,再将第一个连接在方法返回的字符串的尾部,依次类推,递归完成所有步骤。

注意考虑条件出口,不然将产生输入异常。

当字符串只剩下一个时就结束递归,此时考虑只剩一个时需要返回的元素,和返回条件。


public class receverString {
	public static String recever(String s) {
		if(s.length()<=1) {
			return s;
		}
		return recever(s.substring(1))+s.charAt(0);
	}
	public static void main(String[] args) {
		String s = "abcd";
		System.out.println(recever(s));
	}
}

递归打印

递归的思路很单一,只需考虑什么情况下继续递归,什么情况下结束递归并返回所需要元素。上题从正面考虑,同样我们可以反向考虑,设置条件继续递归,否则结束递归。

//从一打印到十
public class printRecursion {
	public static void Print(int a,int b) {
		if(a < b) {
			Print(a,b-1);
		}
		System.out.print(b);
	}
	public static void main(String[] args) {
		Print(1,10);
	}
}

真题训练

出栈次序

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

X星球太死板,要求每辆路过的车必须进入检查站,也可能不检查就放行,也可能仔细检查。
如果车辆进入检查站和离开的次序可以任意交错。那么,该车队再次上路后,可能的次序有多少种?

在这里插入图片描述
思路:我们可以把问题简化为:
两个变量“待检查的车辆”和“正在检查的车辆”。已经检查的车辆不影响解题过程则忽略。

那么将会发生两种情况:
第一“待检查车辆”进入检查站开始检查,
第二“正在检查车辆”结束检查出栈。
此时,递归所要考虑的两个问题,已经解决一个了。

再考虑什么时候需要结束递归:
有两种情况:
第一“待检查车辆”已经都进入检查,一种检查方式结束,返回值为1。
第二“正在检查车辆”为空,“待检查车辆”还有剩余,则检查方式未结束,继续将一辆“待检查车辆”传入检查站。
至此,可以计算出检查方法的总和。

public class stackIssue {
//n为待检查车辆
//m为正在检查车辆
	public static int stack(int n,int m) {
		if(n <= 0)
			return 1;
		if(m <= 0)
			return stack(n-1,1);
		return stack(n-1,m+1) + stack(n,m-1);
	}
	public static void main(String[] args) {
		for(int i = 1;i<17;i++) {
			System.out.println(i+":"+stack(i,0));
		}
	}
}

第39级台阶

【问题描述】 小明刚刚看完电影《第39级台阶》。离开电影院的时候,他数了数礼堂前的台阶数,恰好是39级! 站在台阶前,他突然又想着一个问题:
如果我每一步只能迈上1个或2个台阶。先迈左脚,然后左右交替,最后一步是迈右脚,也就是说一共要走偶数步。那么,上完39级台阶,有多少种不同的上法呢?
请你利用计算机的优势,帮助小明寻找答案。

思路:问题简化:小明上台阶此时需要有两种方式,两种方式各有两种情况,这就需要两个方法,相互递归调用。

第一种方式:小明左脚上台阶,此时可以走两个台阶,也可以走一个台阶。
第二种方式:小明右脚上台阶,此时可以走两个台阶,也可以走一个台阶。

我们可以设置两个方法,一个为左脚,一个为右脚,当左脚调用结束时,需要调用右脚,右脚同理。
此时,我们解决了递归的第一个问题。

什么时候递归结束:
当台阶数为0时,若此时在左脚方法内,则最后为左脚站在最后一阶台阶,说明一共走的不是偶数步,不能算作一种方式,返回值为0。
当台阶数为0时,若此时在右脚方法内,则最后为右脚站在最后一阶台阶,说明一共走的是偶数步,算作一种方式,返回值为1。

当台阶数为1时,若此时在右脚方法内,则还需走一步,说明最后左脚在最后一节台阶,一共走的不是偶数步,不能算作一种方式,返回值为0。
当台阶数为1时,若此时在左脚方法内,则还需走一步,说明最后右脚在最后一节台阶,一共走的是偶数步,算作一种方式,返回值为1。
至此,可以计算出走步方式的总和。

public class _39Steps {
	public static int leftSteps(int n) {
		if(n <= 0)
			return 0;
		if(n == 1)
			return 1;
		return rightSteps(n-1)+rightSteps(n-2);
	}
	public static int rightSteps(int n) {
		if(n <= 0)
			return 1;
		if(n == 1)
			return 0;
		return leftSteps(n-1)+leftSteps(n-2);
	}
	public static void main(String[] args) {
		System.out.println(rightSteps(39));
	}
}

算式填符号

【问题描述】 匪警请拨110,即使手机欠费也可拨通!
为了保障社会秩序,保护人民群众生命财产安全,警察叔叔需要与罪犯斗智斗勇,因而需要经常性地进行体力训练和智力训练!
某批警察叔叔正在进行智力训练: 1 2 3 4 5 6 7 8 9 = 110
请看上边的算式,为了使等式成立,需要在数字间填入加号或者减号(可以不填,但不能填入其它符号)。之间没有填入符号的数字组合成一个数,例如:12+34+56+7-8+9
就是一种合格的填法;123+4+5+67-89 是另一个可能的答案。
请你利用计算机的优势,帮助警察叔叔快速找到所有答案。
每个答案占一行。形如:
12+34+56+7-8+9
123+4+5+67-89
……

思路:问题简化:问题需要考虑三种情况,需要在元素的空隙位置填入“+”、“-”、无,并且需要输出拼好的字符串和限制算数结果。
按条件所需,从目标值开始,需要填入“+”则减去对应元素,需要“-”则加上对应元素,需要拼组,则组装传入下一层递归。
根据所给出的元素数组,我们从后向前考虑更好,可以避免负数类的条件,考虑的条件更少。
此时,我们解决了递归的第一个问题。

什么时候递归结束:
当所有元素用完,并且计算出的数字与题目要求相符,打印拼装出的字符串。
至此,可以得到所有满足题目要求的算式字符串。


public class arithmeticFiller {
	public static void filler(int[] element,int locat,String result,int goal) {
		if(locat <= 0) {
			if(goal == element[0]) {
				System.out.println(element[0]+result);
			}
			return;
		}
		filler(element,locat-1,"+"+element[locat]+result,goal-element[locat]);
		filler(element,locat-1,"-"+element[locat]+result,goal+element[locat]);
		
		int temp = element[locat-1];
		element[locat-1] = Integer.parseInt(""+element[locat-1]+element[locat]);
		filler(element,locat-1,result,goal);
		element[locat-1] = temp;
		
	}
	public static void main(String[] args) {
		int[] a = {1,2,3,4,5,6,7,8,9};
		filler(a,8,"",110);
	}
}

找钱问题

【问题描述】 公园票价为5角。假设每位游客只持有两种币值的货币:5角、1元。 再假设持有5角的有m人,持有1元的有n人。
由于特殊情况,开始的时候,售票员没有零钱可找。 我们想知道这m+n名游客以什么样的顺序购票则可以顺利完成购票过程。 显然,m <n的时候,无论如何都不能完成; m>=n的时候,有些情况也不行。比如,第一个购票的乘客就持有1元。
请计算出这m+n名游客所有可能顺利完成购票的不同情况的组合数目。
注意:只关心5角和1元交替出现的次序的不同排列,持有同样币值的两名游客交换位置并不算做一种新的情况来计数。

思路:由题可知,需要有两个变量m、n,分别记录1元和5角的人数,那么我们可以从最后一人向前考虑,每次结账将会少一个人,这个人不是五角就是一元,这两种情况,可以递归实现。
此时,我们解决了递归的第一个问题。

什么时候递归结束:
由题意可知,当m<n时无论如何是无法实现的,可以作为一个结束条件。
当m减少到1时,说明此时必然第一个人为5角,可以实现,作为一种方法,返回值为1。
当n减少至0时,说明此时m也为0或是只剩下m的人,说明5角有盈余,可以实现为一种方法,返回值为1。


public class giveChange {
	public static int change(int m,int n) {
		if(m<n)
			return 0;
		if(m<=1)
			return 1;
		if(n==0)
			return 1;
		return change(m-1,n)+change(m,n-1);
	}
	public static void main(String[] args) {
		System.out.println(change(2,2));
	}
}

振兴中华

【问题描述】 小明参加了学校的趣味运动会,其中的一个项目是:跳格子。 地上画着一些格子,每个格子里写一个字,如下所示:(也可参见下图)

从我做起振
我做起振兴
做起振兴中
起振兴中华

比赛时,先站在左上角的写着“从”字的格子里,可以横向或纵向跳到相邻的格子里,但不能跳到对角的格子或其它位置。一直要跳到“华”字结束。
要求跳过的路线刚好构成“从我做起振兴中华”这句话。 请你帮助小明算一算他一共有多少种可能的跳跃路线呢?

思路:我们可以根据题目所给出的图,尝试行走几条路线,会发现,无论从那条路线出发,严格按照只向右和向下,走到右下角必然会组成“从我做起振兴中华”这句话。
那么我们可以不必拘泥于实现走的过程,只要计算从左上角到右下角共有多少种走法即可。
那么我们从终点开始考虑,每次无论向上,还是向左,减少一格即可。
此时,我们解决了递归的第一个问题。

什么时候递归结束:
设置两个变量,分别表示行、列,当行或列有一项到达第一行时,就说明实现了一种方法,返回值为1。

public class magicString {
	public static int conbination(int x,int y) {
		if(x == 1 || y == 1)
			return 1;
		return conbination(x-1,y)+conbination(x,y-1);
	}
	public static void main(String[] args) {
		System.out.println(conbination(5,4));
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值