递归:
递归:你打开面前这扇门,看到屋里面还有一扇门(这门可能跟前面打开的门一样大小(静),也可能门小了些(动)),你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,你继续打开,。。。, 若干次之后,你打开面前一扇门,发现只有一间屋子,没有门了。 你开始原路返回,每走回一间屋子,你数一次,走到入口的时候,你可以回答出你到底用这钥匙开了几扇门。
循环:你打开面前这扇门,看到屋里面还有一扇门,(这门可能跟前面打开的门一样大小(静),也可能门小了些(动)),你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,(前面门如果一样,这门也是一样,第二扇门如果相比第一扇门变小了,这扇门也比第二扇门变小了(动静如一,要么没有变化,要么同样的变化)),你继续打开这扇门,。。。,一直这样走下去。 入口处的人始终等不到你回去告诉他答案。
该用户这么总结到:
递归就是有去(递去)有回(归来)。
具体来说,为什么可以”有去“?
这要求递归的问题需要是可以用同样的解题思路来回答除了规模大小不同其他完全一样的问题。
为什么可以”有回“?
这要求这些问题不断从大到小,从近及远的过程中,会有一个终点,一个临界点,一个baseline,一个你到了那个点就不用再往更小,更远的地方走下去的点,然后从那个点开始,原路返回到原点。
上面的解释几乎回答了我已久的疑问:为什么我老是有递归没有真的在解决问题的感觉?
因为递是描述问题,归是解决问题。而我的大脑容易被递占据,只往远方去了,连尽头都没走到,何谈回的来。
《漫谈递归:递归的思想》这篇文章将递归思想归纳为:
递归的基本思想是把规模大的问题转化为规模小的相似的子问题来解决。在函数实现时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况。另外这个解决问题的函数必须有明显的结束条件,这样就不会产生无限递归的情况了。
需注意的是,规模大转化为规模小是核心思想,但递归并非是只做这步转化,而是把规模大的问题分解为规模小的子问题和可以在子问题解决的基础上剩余的可以自行解决的部分。而后者就是归的精髓所在,是在实际解决问题的过程。
我试图把我理解到递归思想用递归用程序表达出来,确定了三个要素:递 + 结束条件 + 归。
function recursion(大规模)
{
if (end_condition)
{
end ;
}
else
{ //先将问题全部描述展开,再由尽头“返回”依次解决每步中剩余部分的问题
recursion(小规模); //go;
solve; //back;
}
}
|
但是,我很容易发现这样描述遗漏了我经常会遇到的一种递归情况,比如递归遍历的二叉树的先序。我将这种情况用如下递归程序表达出来。
function recursion(大规模)
{
if (end_condition)
{
end ;
}
else
{ //在将问题转换为子问题描述的每一步,都解决该步中剩余部分的问题。
solve; //back;
recursion(小规模); //go;
}
}
|
其实理解递归可能没有“归”,只有去(分治)的情况后,我们应该想到递归也许可以既不需要在“去”的路上解决问题,也不需要在“归”的路上解决问题,只需在路的尽头解决问题,即在满足停止条件时解决问题。递归的分治思想不一定是要把问题规模递归到最小,还可以是将问题递归穷举其所有的情形,这时通常递归的表达力体现在将无法书写的嵌套循环(不确定数量的嵌套循环)通过递归表达出来。
将这种递归情形用递归程序描述如下:
recursion()
{
if (end_condition)
{
solve;
}
else
{ //在将问题转换为子问题描述的每一步,都解决该步中剩余部分的问题。
for () { recursion(); //go; }
}
}
|
二、常见的递归问题
1.汉诺塔问题
Move 2 from A to C
Move 1 from B to C
Move 2 from A to B
Move 1 from C to B
Move 3 from A to C
Move 1 from B to A
Move 2 from B to C
Move 1 from A to C
源代码
2、n的阶乘
3、Fibonacci数列
无穷数列1,1,2,3,5,8,13,21,34,55, … ,被称为Fibonacci数列。它可以递归地定义为:
4、集合元素的全排列
5、正整数的划分
将正整数n表示成一系列正整数之和:n=n1 +n2+ … +nk ,其中n1≥n2≥ … ≥nk≥1,k≥1,这种表示称为正整数n的划分。求正整数n的不同划分个数。
6、Ackerman函数
当一个函数及其它的变量是由函数自身定义时,称这个函数是双递归函数。