数据结构与算法(4)递归

阅读极客时间《数据结构与算法之美》总结

递归的概念

递归就是将一个问题分解为子问题后,通过不断调用子问题的子问题的…的子问题,知道达到出口条件后得到问题的答案的过程。感觉像是一个螺旋下降的过程,从上往下看,就是不断的自我循环,但是从侧面看的话,就是一个不断向深、向下求解的过程。递归的条件如下:

  1. 问题能够分解为子问题。
  2. 所有子问题即使数据规模不一致,但是其解决思路是一致的,就像前面说的一样,是不断的自我循环(只不过数字不一样罢了)
  3. 要有出口!递归就是方法调用方法,只不过是调用方法是一样的罢了,但是(Java视角)在虚拟机看来,其调用的都是方法, 既然调用方法,那么就需要进行入栈、出栈的操作,如果没有出口的话,那么虚拟机栈就会溢出,OutOfMemory异常!所以必须要有出口。

递归的自我理解

递归过程

结合文中的例子说明一下:
一个人可以走一级或两级台阶,那么问一个n级台阶,有多少种方法呢?

public int f(int n){
	if(n==1) return 1;
	if(n==2) return 2;
	return f(n-1)+f(n-2);
}

在我看来分解子问题就是将问题分解为一步一步的过程,我们只要考虑在上一步完成后如何达到下一步就好了(也就是求解递归公式)。在这个问题里面,我们在走完上一步后,可以有两种选择,一种是走一步,另一种是走两步。既然要走一步,那么我在上一步中(也就是子问题)已经走了n-1步了,所以在当前步来说,走一步的方案就是f(n-1);另一种就是走两步,同理,既然要走两步,那么在上一步中我已经走了n-2步了,所以在当前步来说,走两步的方案就是f(n-2)。f(n)的共有两种方案,两者加起来就是f(n)的方法了,所以f(n)=f(n-1)+f(n-2)。
出口条件呢?我们想一下:

f ( n ) = f ( n - 1 ) + f ( n - 1 ) ;
f ( n - 1 ) = f ( n - 2 ) + f ( n - 3 ) ; 
f ( n - 2 ) = f ( n - 3 ) + f ( n - 4 ) ;
·
·
·
f ( 3 ) = f ( 2 ) + f ( 1 ) ;

可以看出不论如何,出现的f()的参数都是连续的,而且对于n来说要有两个比它连续小的数,那么我们最终1、2是必然出现的(我们不考虑0)因为在这里1是数字的开头,而要出现两个连续比n小的数,所以1、2要作为比n小的数,所以对于n>=3来说,可以用f ( n ) = f ( n - 1 ) + f ( n - 2 )来进行计算。但是1、2该怎么办呢,可以用出口条件进行安排呀,因为f(1)=1,f(2) = 2;这样对于n>=1的自然数来说都可以用了。

递归向循环转换

我理解的递归思路其实分为两步:

  1. 不断向下挖掘,找到最根本的问题,也就是上面例子中的f(1)和f(2)了。
  2. 根据最根本的问题不断向上反向求解出子问题的父问题。
    开头我提出了一个螺旋下降的模型,那么形象的理解一下,第一步就是坐着这个螺旋的滑梯溜到底,然后再顺着滑梯爬上去。
    顺着这个思路,为什么第一步要到最根本的问题(溜到底)呢?因为最底下的问题是出口,出口可以计算得到值!然后将这个值返回到上一层的问题,这就是反向求解出父问题了。其实在父问题中,这个子问题的递归调用f(n-1)和f(n-2)还起到了中间变量的作用,保存了上一步的变量的值,然后构造新值,这个新值又作为更上一层的中间变量,这样就发现了一个规律,其实每一层调用的时候都会有f(n-1)和f(n-2)(虽然在虚拟机中这两个指向的内存地址不一样,但是我们只从逻辑上想象一下),那我们是不是可以想像在我们第二步中(就是从滑梯底部向上爬的时候)就是不断更新这两个中间变量,最终达到最上层后得到结果呢?
    顺着这个思路,我们就可以把递归转换为循环形式了:
public int f(n){
	int result = 0;
	int fn_1 = 2;
	int fn_2 = 1;
	for(int i = 3;i < n;++i){
		result = fn_1 + fn_2;
		fn_2 = fn_1;
		fn_1 = result;
	}
	return result;
}

result就是我们每一层得到的结果,fn_2就是在上一层要用到的f(n-2),因为上一层的f(n-2)就是这层的f(n-1),所以fn_2 = fn_1;同理,这层的结果result就是上一层的fn_1,所以fn_1 = result;
同递归不一样的是,我们用递归写的时候相当于我们已经达到最下一层了,所以我们一开始会对fn_1和fn_2进行赋值,之后进行的步骤就是不断向上求解(每次都要提前计算出fn_1,fn_2,这样到上一层后直接接可以进行计算了,当然也可以到了上一层再进行计算,就像我下面写的一样:)

public int f(n){
	int result = 0;
	int fn_1 = 0;
	int fn_2 = 0;
	for(int i = 1;i < n;++i){
		if(i==1){
			 result = 1;
		}else if(i == 2){
			result = 2;
			fn_1 = 1;
		}else{
			fn_2 = fn_1;
			fn_1 = result;
			result = fn_1 + fn_2;
	}
	return result;
}

其实从递归向循环转变的最重要的思路就是将递归的中间计算结果保存下来就行了,然后再向上一层计算的时候把计算结果进行一定的更新就够了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值