递归啊递归~

        递归对于一部分初学者来说是一个门槛,也有很多人讲怎么学递归,出于怎么能让学生学会递归的角度跟着学了很多。个人觉得,学习递归还是要知其然知其所以然;当然,这个所以然要有一个度,咱要是拿出来调试工具去跟踪那一定不是教初学编程的人;但是,这个度太浅似乎也不行,如果只罗列一些结论那么这些知识可能不如不讲。个人觉得,要让学生“感觉”:嘿我懂了,它是这么回事……这样,他们在现阶段的知识就有“基石”,什么时候他们去真的深究到底CPU这么处理这些数据流、内存中表现是如何的、我咋能挂个钩子……之类的东西的时候,可能这些“基石”才会真正垫在底下。但在这之前,虚的基石让他们觉得这个事我清楚它背后的原理(我不用但是我懂……其实也不懂),那么他们可能就能以“更高的视角”、“至少不畏惧”的心态上把上层的、代码层面的递归学得更好一些。

        个人观点,就像尊该尊之老爱可爱之幼一样,传授知识也一样——造可造之才。内动力才是最重要的,学生想学会是最大的前提。

        个人观点,学递归之前,还是要把函数的定义、调用、返回这些基础知识学牢靠。

一、举例子,先对递归这个东西有点赶脚!

        什么是递归呢,就像用递归形式定义的阶乘公式:n=n*(n-1)!。就像:

1、查字典来理解一个词A,但是这些解释里面有一个词B不懂啊,咋办?

2、查字典来理解一个词B,但是这些解释里面有一个词C不懂啊,咋办?

3、恭喜你都会抢答了

4、直到某一次,例如:D这个词的解释里面没有不理解的词了。

        这时候咋整呢?

5、依据D的解释理解了C这个词

6、依据C的解释理解了B这个词

7、依据B的解释理解了A这个词

至此,完成了理解词A这个任务。

        这个过程就是递归,1→3就是递、5→7就是归。4呢?它是一个比较特殊的过程,递进到4的时候马上就回归了。

二、从另一个角度来看一下

        可能这个过程不是对于每个人都是必要的。但有一部分人依靠这个过程可以更好的理解递归。放下递归不管,我们写一堆函数:

这些函数就是我们做的事情,意思意思:

        如上图所示,FnA()会沿着红线调用FnB(),FnB()调用FnC(),FnC()调用FnD(),FnD直接返回结果、无调用;FnC()执行完对FnD()的调用返回给FnB(),同样的FnB()执行完对FnC()的调用返回给FnA(),FnA()执行结束返回。

        现在,我们来看一下FnA()、FnB()、FnC()做的事情:调用一个函数获得结果加工后返回——功能一样代码相同;但FnD()不一样——因为查字典的时候D的解释中没有不懂的词,所以不用调用类似FnA()它们的过程。

        那么,代码是指令它们存在于可执行内存不会因执行而改变(这段划掉)由于代码相同所以我们把FnA()、FnB()、FnC()保留一个就可以了——因为C++会保证我们调用时不会出现混乱:它处理的方式很简单,函数就在那里就像一个工厂,数据进来之后就像原材料被加工成产品再出来;那么,当这一批产品没生产完下一批来了怎么办?先加工新来的这一批才能保证顺序不混乱,那之前加工的或未加工的数据怎么办?多数情况下新一批产品加工完还需要继续加工,这时我们按照一定规则把它们保存起来就可以,C++的做法是把它们一层一层摞起来,拿的时候从上面依次拿下来。

        所以,写一个函数Fn()来完成所有任务是没有问题的,如果我们把FnD()也写进来,这个函数功能看起来就完整了:只需要if一下,当不需要调用Fn()时直接返回结果即可。

三、稍微专业点

        FnD()的特殊性在于:它是边界——查找的时候我们遇到了简单情况(可以直接返回结果的情况)。很多递归函数都存在显式边界。

        其他情况,都需要进行递归调用——未到达边界时,使用新的数据来调用Fn()函数。

        根据我们之前的分析:

1、虽然我们没有编写完Fn()函数,但它的功能就是在字典里查一个词,然后返回其释义。所以,不要忘了这个函数是做什么的——当你需要这个功能就调用这个函数。当然,如果你已经有把大问题分解为小问题,在适合的地方使用函数来表达的基本功,这很简单——你经常把功能实现留下来一个空函数不是吗?

2、前面的描述中,Fn()缺点什么呢?它的功能就是在字典里查一个词,然后返回其释义。所以,我们需要一个参数——因为查字典至少要直到查哪个单词。

四、总结一下

1、首先,明确函数功能是做什么的——你需要处理什么?回到最初的例子:处理递归形式定义的阶乘:我要求一个数的阶乘。

2、连带的,参数是什么?我要求一个数的阶乘,至少要知道这个数。

3、什么时候递归呢,需要的时候^ ^,别别别吐芬芳……它确实是这么个事,但和另一面——边界紧密相关,什么时候不需要再递归了呢?到达边界时。

        边界在哪?以递归形式定义的阶乘为例,随着计算进行每次传入的数越来越小,定义中规定1的阶乘为1——不需要计算了直接返回即可。

4、返回,回归的时候回归的是什么呢?思考一下看看混乱了没有……每次调用(最初的时候我们有很多函数)是干什么的?很简单呀,把计算传进来的那个数的阶乘返回呀,函数不就是这个功能吗?

五、上代码

int Jc(int x){
	if(x==1){		//边界:1!=1 
		return 1; 	
	}else{			//x!=x*(x-1)! 
		return x*Jc(x-1);
	}
}

        C++会自动维护一个栈——后进先出(先进后出)的集合,来确保函数调用不会导致混乱。每次调用时先把当前状态保存进去,然后进入新的函数——取得所需参数然后运行——运行结束回到之前保存的状态。这个过程可以嵌套,即新函数再调用其他函数时,也做相同的事情。

六、一些表示法

        如前所述,我们使用了一个流程图把递归调用展开,但其实这不是个好办法。个人认为,对于初学者来说,应该从工厂加工产品角度来看这个东西,即只关注当前函数在干什么而不跟踪调用——如果跟踪很容易糊了。但有些方法值得我们学习:

1、用树来表示调用……呃有点那什么哈

2、用树……对用类似于括号化定理的那个结构——把它展开一下就可以写更多东西,而且还很直观

        这个结构容易嵌套,并且有助于把过程写清楚。

七、强化一下

        据我所知,都做过数字翻转。代码大概长这样:

int main(){
	int x;
	cin>>x;
	int ans=0;
	while(x){
		ans=ans*10+x%10;
		x/=10;
	}
	cout<<ans;
	return 0;
}

        那么,下面哪些代码会输出翻转的数字呢:

void Function(int x){
	if(x==0){
		return;
	}else{
		cout<<x%10;	
		Function(x/10);	
	}
}
void Function(int x){
	if(x==0){
		return;
	}else{
		Function(x/10);	
		cout<<x%10;	
	}
}
void Function(int x){
	if(x<10){
		cout<<x;
		return;
	}else{
		cout<<x%10;
		Function(x/10);		
	}
}
void Function(int x){
	if(x<10){
		cout<<x;
		return;
	}else{
		Function(x/10);	
		cout<<x%10;	
	}
}

  若x的值包含0在内,应该只有一个正确。若包含负数呢?思考之后自己测试一下吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清晨曦月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值